Fred808's picture
Upload 256 files
7a0c684 verified
import struct
from .shader_compiler import ShaderCompiler
from .rasterizer import Rasterizer
class GraphicsAPI:
def __init__(self, driver):
self.driver = driver
self._verify_driver_compatibility()
# Resource tracking
self.buffers = {}
self.textures = {}
self.framebuffers = {}
self.programs = {}
# Current state
self.current_program = None
self.current_framebuffer = None
self.current_texture_units = {}
# Initialize subsystems
self.shader_compiler = ShaderCompiler()
self.rasterizer = Rasterizer(driver)
print("Graphics API initialized.")
def _verify_driver_compatibility(self):
"""Verify driver has required graphics capabilities"""
required_methods = [
'create_buffer',
'create_texture',
'create_framebuffer',
'draw_triangles'
]
missing_methods = [method for method in required_methods
if not hasattr(self.driver, method)]
if missing_methods:
raise RuntimeError(
f"Driver missing required graphics methods: {missing_methods}"
"\nEnsure driver has modern graphics API support."
)
# Verify required enums exist
required_enums = [
'TextureFormat',
'FilterMode',
'WrapMode',
'BufferType',
'MSAASamples'
]
missing_enums = [enum for enum in required_enums
if not hasattr(self.driver, enum)]
if missing_enums:
raise RuntimeError(
f"Driver missing required graphics enums: {missing_enums}"
"\nEnsure driver has modern graphics type support."
)
def create_buffer(self, data, buffer_type="vertex", dynamic=False, map_write=False):
"""
Create a new buffer with data
Args:
data: numpy array or bytes of buffer data
buffer_type: "vertex", "index", "uniform", "storage", or "indirect"
dynamic: Whether buffer will be frequently updated
map_write: Whether buffer should be mappable for CPU writes
"""
if not self.driver.initialized:
raise RuntimeError("Driver not initialized.")
# Convert buffer type string to enum
type_map = {
"vertex": self.driver.BufferType.VERTEX,
"index": self.driver.BufferType.INDEX,
"uniform": self.driver.BufferType.UNIFORM,
"storage": self.driver.BufferType.STORAGE,
"indirect": self.driver.BufferType.INDIRECT
}
if buffer_type not in type_map:
raise ValueError(f"Invalid buffer type: {buffer_type}")
# Create buffer through driver
buffer_id = self.driver.create_buffer(
data=data,
buffer_type=type_map[buffer_type],
dynamic=dynamic,
map_write=map_write
)
# Store additional metadata
self.buffers[buffer_id] = {
"type": buffer_type,
"size": len(data),
"dynamic": dynamic,
"map_write": map_write
}
print(f"Created {buffer_type} buffer with ID {buffer_id}, size {len(data)} bytes.")
return buffer_id
def delete_buffer(self, buffer_id):
if buffer_id in self.buffers:
buffer_info = self.buffers[buffer_id]
self.driver.free_memory(buffer_info["virtual_address"])
del self.buffers[buffer_id]
print(f"Deleted buffer with ID {buffer_id}.")
else:
print(f"Warning: Attempted to delete non-existent buffer with ID {buffer_id}.")
def buffer_data(self, buffer_id, data):
if buffer_id not in self.buffers:
raise ValueError(f"Buffer with ID {buffer_id} not found.")
buffer_info = self.buffers[buffer_id]
if len(data) > buffer_info["size"]:
raise ValueError(f"Data size ({len(data)}) exceeds buffer size ({buffer_info['size']}) for buffer ID {buffer_id}.")
self.driver.write_memory(buffer_info["virtual_address"], data)
print(f"Loaded {len(data)} bytes into buffer ID {buffer_id}.")
def draw_arrays(self, mode, first, count, vertex_buffer,
primitive_restart_index=None, instances=1):
"""
Draw primitives from vertex buffer
Args:
mode: "points", "lines", "triangles", etc
first: First vertex to draw
count: Number of vertices
vertex_buffer: Vertex buffer ID
primitive_restart_index: Index value that restarts primitive
instances: Number of instances to draw
"""
if not self.driver.initialized:
raise RuntimeError("Driver not initialized.")
if not self.current_program:
raise RuntimeError("No shader program in use.")
if not self.current_framebuffer:
raise RuntimeError("No framebuffer bound.")
print(f"Drawing {count} vertices in {mode} mode")
# Draw using driver's optimized path
self.driver.draw_triangles(
vertex_buffer_id=vertex_buffer,
index_buffer_id=None,
framebuffer_id=self.current_framebuffer,
shader_program=self.current_program,
num_vertices=count,
start_vertex=first
)
# Add command to command buffer for replay/debug
self.driver.add_command("draw_arrays",
mode=mode,
first=first,
count=count,
vertex_buffer=vertex_buffer,
primitive_restart_index=primitive_restart_index,
instances=instances
)
def draw_indexed(self, mode, count, vertex_buffer, index_buffer,
index_offset=0, base_vertex=0, instances=1,
primitive_restart_index=None):
"""
Draw indexed primitives
Args:
mode: "points", "lines", "triangles", etc
count: Number of indices
vertex_buffer: Vertex buffer ID
index_buffer: Index buffer ID
index_offset: Starting offset in index buffer
base_vertex: Value added to each index
instances: Number of instances to draw
primitive_restart_index: Index value that restarts primitive
"""
if not self.driver.initialized:
raise RuntimeError("Driver not initialized.")
if not self.current_program:
raise RuntimeError("No shader program in use.")
if not self.current_framebuffer:
raise RuntimeError("No framebuffer bound.")
if index_buffer not in self.buffers or self.buffers[index_buffer]["type"] != "index":
raise ValueError(f"Invalid index buffer ID: {index_buffer}")
print(f"Drawing {count} indices in {mode} mode")
# Draw using driver's optimized path
self.driver.draw_triangles(
vertex_buffer_id=vertex_buffer,
index_buffer_id=index_buffer,
framebuffer_id=self.current_framebuffer,
shader_program=self.current_program,
num_vertices=count,
start_vertex=index_offset
)
# Add command to command buffer
self.driver.add_command("draw_indexed",
mode=mode,
count=count,
vertex_buffer=vertex_buffer,
index_buffer=index_buffer,
index_offset=index_offset,
base_vertex=base_vertex,
instances=instances,
primitive_restart_index=primitive_restart_index
)
def compile_shader(self, shader_source, shader_type="vertex"):
return self.shader_compiler.compile_shader(shader_source, shader_type)
def link_program(self, vertex_shader, fragment_shader):
return self.shader_compiler.link_program(vertex_shader, fragment_shader)
def use_program(self, program):
if not self.shader_compiler.validate_program(program):
raise ValueError("Invalid shader program.")
self.current_program = program
print(f"Using shader program: {program['id']}.")
def create_texture(self, width, height, format="rgba8",
filter="bilinear", wrap="repeat",
generate_mipmaps=True, aniso_level=1):
"""
Create a new texture
Args:
width, height: Texture dimensions
format: "r8", "rgba8", "rgba16f", "rgba32f", etc.
filter: "nearest", "bilinear", "trilinear", "anisotropic"
wrap: "repeat", "clamp", "mirror"
generate_mipmaps: Whether to generate mipmaps
aniso_level: Anisotropic filtering level (1-16)
"""
# Convert string parameters to enums
format_map = {
"r8": self.driver.TextureFormat.R8,
"rg8": self.driver.TextureFormat.RG8,
"rgba8": self.driver.TextureFormat.RGBA8,
"r16f": self.driver.TextureFormat.R16F,
"rgba16f": self.driver.TextureFormat.RGBA16F,
"r32f": self.driver.TextureFormat.R32F,
"rgba32f": self.driver.TextureFormat.RGBA32F,
"bc1": self.driver.TextureFormat.BC1,
"bc3": self.driver.TextureFormat.BC3
}
filter_map = {
"nearest": self.driver.FilterMode.NEAREST,
"bilinear": self.driver.FilterMode.BILINEAR,
"trilinear": self.driver.FilterMode.TRILINEAR,
"anisotropic": self.driver.FilterMode.ANISOTROPIC
}
wrap_map = {
"repeat": self.driver.WrapMode.REPEAT,
"clamp": self.driver.WrapMode.CLAMP,
"mirror": self.driver.WrapMode.MIRROR
}
if format not in format_map:
raise ValueError(f"Invalid texture format: {format}")
if filter not in filter_map:
raise ValueError(f"Invalid filter mode: {filter}")
if wrap not in wrap_map:
raise ValueError(f"Invalid wrap mode: {wrap}")
texture_id = self.driver.create_texture(
width=width,
height=height,
format=format_map[format],
filter_mode=filter_map[filter],
wrap_mode=wrap_map[wrap],
generate_mipmaps=generate_mipmaps,
aniso_level=aniso_level
)
print(f"Created texture with ID {texture_id}, format {format}, size {width}x{height}")
return texture_id
def create_framebuffer(self, width, height, color_formats=None,
depth_format=None, stencil_format=None,
samples=1):
"""
Create a new framebuffer
Args:
width, height: Framebuffer dimensions
color_formats: List of formats for color attachments
depth_format: Format for depth attachment (None to disable)
stencil_format: Format for stencil attachment (None to disable)
samples: MSAA sample count (1, 2, 4, or 8)
"""
# Default color format if none specified
if color_formats is None:
color_formats = ["rgba8"]
# Convert format strings to enums
format_map = {
"r8": self.driver.TextureFormat.R8,
"rgba8": self.driver.TextureFormat.RGBA8,
"rgba16f": self.driver.TextureFormat.RGBA16F,
"r32f": self.driver.TextureFormat.R32F
}
samples_map = {
1: self.driver.MSAASamples.MSAA_1X,
2: self.driver.MSAASamples.MSAA_2X,
4: self.driver.MSAASamples.MSAA_4X,
8: self.driver.MSAASamples.MSAA_8X
}
# Convert formats to enums
color_format_enums = []
for fmt in color_formats:
if fmt not in format_map:
raise ValueError(f"Invalid color format: {fmt}")
color_format_enums.append(format_map[fmt])
depth_format_enum = None
if depth_format:
if depth_format not in format_map:
raise ValueError(f"Invalid depth format: {depth_format}")
depth_format_enum = format_map[depth_format]
stencil_format_enum = None
if stencil_format:
if stencil_format not in format_map:
raise ValueError(f"Invalid stencil format: {stencil_format}")
stencil_format_enum = format_map[stencil_format]
if samples not in samples_map:
raise ValueError(f"Invalid sample count: {samples}")
# Create framebuffer through driver
fb_id = self.driver.create_framebuffer(
width=width,
height=height,
color_formats=color_format_enums,
depth_format=depth_format_enum,
stencil_format=stencil_format_enum,
samples=samples_map[samples]
)
# Store framebuffer info
self.framebuffers[fb_id] = {
"width": width,
"height": height,
"color_formats": color_formats,
"depth_format": depth_format,
"stencil_format": stencil_format,
"samples": samples
}
print(f"Created framebuffer with ID {fb_id}, size {width}x{height}, {samples}x MSAA")
return fb_id
def bind_framebuffer(self, fb_id):
"""Bind framebuffer for rendering"""
if fb_id not in self.framebuffers:
raise ValueError(f"Invalid framebuffer ID: {fb_id}")
self.current_framebuffer = fb_id
fb_info = self.framebuffers[fb_id]
print(f"Binding framebuffer {fb_id} ({fb_info['width']}x{fb_info['height']})")
def bind_texture(self, texture_id, unit=0):
"""Bind texture to a texture unit"""
if texture_id not in self.textures:
raise ValueError(f"Invalid texture ID: {texture_id}")
self.current_texture_units[unit] = texture_id
print(f"Binding texture {texture_id} to unit {unit}")
def set_viewport(self, x, y, width, height):
"""Set viewport for rendering"""
if not self.current_framebuffer:
raise RuntimeError("No framebuffer bound")
fb_info = self.framebuffers[self.current_framebuffer]
if x + width > fb_info['width'] or y + height > fb_info['height']:
raise ValueError("Viewport exceeds framebuffer dimensions")
self.current_viewport = {
'x': x, 'y': y,
'width': width, 'height': height
}
def clear(self, color=None, depth=None, stencil=None):
"""Clear current framebuffer attachments"""
if not self.current_framebuffer:
raise RuntimeError("No framebuffer bound")
fb_id = self.current_framebuffer
fb = self.framebuffers[fb_id]
# Get framebuffer from driver
driver_fb = self.driver.framebuffer_manager[fb_id]
if color is not None:
r, g, b, a = color
driver_fb.set_clear_values(color=[r, g, b, a])
if depth is not None:
driver_fb.set_clear_values(depth=depth)
if stencil is not None:
driver_fb.set_clear_values(stencil=stencil)
# Perform clear
driver_fb.clear(
color=color is not None,
depth=depth is not None,
stencil=stencil is not None
)
print(f"Cleared framebuffer {fb_id}")
def set_blend_state(self, enable=True,
src_factor="src_alpha",
dst_factor="one_minus_src_alpha",
blend_op="add"):
"""Set blending state"""
if not self.current_framebuffer:
raise RuntimeError("No framebuffer bound")
# Convert string parameters to driver enums
factor_map = {
"zero": self.driver.BlendMode.ZERO,
"one": self.driver.BlendMode.ONE,
"src_color": self.driver.BlendMode.SRC_COLOR,
"one_minus_src_color": self.driver.BlendMode.ONE_MINUS_SRC_COLOR,
"dst_color": self.driver.BlendMode.DST_COLOR,
"one_minus_dst_color": self.driver.BlendMode.ONE_MINUS_DST_COLOR,
"src_alpha": self.driver.BlendMode.SRC_ALPHA,
"one_minus_src_alpha": self.driver.BlendMode.ONE_MINUS_SRC_ALPHA,
"dst_alpha": self.driver.BlendMode.DST_ALPHA,
"one_minus_dst_alpha": self.driver.BlendMode.ONE_MINUS_DST_ALPHA
}
op_map = {
"add": self.driver.BlendOp.ADD,
"subtract": self.driver.BlendOp.SUBTRACT,
"reverse_subtract": self.driver.BlendOp.REVERSE_SUBTRACT,
"min": self.driver.BlendOp.MIN,
"max": self.driver.BlendOp.MAX
}
if src_factor not in factor_map:
raise ValueError(f"Invalid source blend factor: {src_factor}")
if dst_factor not in factor_map:
raise ValueError(f"Invalid destination blend factor: {dst_factor}")
if blend_op not in op_map:
raise ValueError(f"Invalid blend operation: {blend_op}")
fb_id = self.current_framebuffer
driver_fb = self.driver.framebuffer_manager[fb_id]
# Store blend state
self.blend_state = {
'enable': enable,
'src_factor': factor_map[src_factor],
'dst_factor': factor_map[dst_factor],
'blend_op': op_map[blend_op]
}
print(f"Set blend state: {enable}, {src_factor}, {dst_factor}, {blend_op}")