Harbor

branch main
showing the latest snapshot on main
backend.odin 17.8 KB · Plain text
gpu/backend/backend.odin 0644 Raw
package backend

import glsl "core:math/linalg/glsl"

// --- Opaque handle types ---

Pipeline_Handle :: distinct u64
Buffer_Handle :: distinct u64
Texture_Handle :: distinct u64
Shader_Handle :: distinct u64
Descriptor_Handle :: distinct u64
Render_Pass_Handle :: distinct u64
Framebuffer_Handle :: distinct u64
Sampler_Handle :: distinct u64

NULL_PIPELINE :: Pipeline_Handle(0)
NULL_BUFFER :: Buffer_Handle(0)
NULL_TEXTURE :: Texture_Handle(0)
NULL_SHADER :: Shader_Handle(0)
NULL_DESCRIPTOR :: Descriptor_Handle(0)
NULL_RENDER_PASS :: Render_Pass_Handle(0)
NULL_FRAMEBUFFER :: Framebuffer_Handle(0)
NULL_SAMPLER :: Sampler_Handle(0)

MAX_FRAMES_IN_FLIGHT :: 2
MAX_COLOR_TARGETS :: 8
COLOR_WRITE_MASK_ALL :: u8(0x0F)

Capability :: enum {
	Compute_Dispatch,
	Storage_Buffers,
	Indirect_Draws,
	Offscreen_Targets,
	Multiple_Render_Targets,
	Stencil_Clips,
	Sampled_Targets,
}
Capability_Flags :: bit_set[Capability]

Capabilities :: struct {
	features:               Capability_Flags,
	max_color_targets:      u32,
	max_push_constant_size: u32,
	max_frames_in_flight:   u32,
}

no_capabilities :: proc() -> Capabilities {
	return {}
}

implemented_base_capabilities :: proc(
	max_color_targets, max_push_constant_size: u32,
) -> Capabilities {
	features := Capability_Flags{.Compute_Dispatch, .Storage_Buffers, .Indirect_Draws, .Offscreen_Targets, .Sampled_Targets, .Stencil_Clips}
	if max_color_targets > 1 {
		features += {.Multiple_Render_Targets}
	}
	return {
		features = features,
		max_color_targets = max_color_targets,
		max_push_constant_size = max_push_constant_size,
		max_frames_in_flight = MAX_FRAMES_IN_FLIGHT,
	}
}

supports :: proc(caps: Capabilities, capability: Capability) -> bool {
	return capability in caps.features
}

supports_color_target_count :: proc(caps: Capabilities, count: u32) -> bool {
	return count == 0 || count <= caps.max_color_targets
}

supports_push_constant_size :: proc(caps: Capabilities, size: u32) -> bool {
	return size == 0 || size <= caps.max_push_constant_size
}

handle_index :: proc(handle: $T, max_count: int) -> (int, bool) {
	idx := int(u64(handle))
	if idx <= 0 || idx >= max_count {
		return 0, false
	}
	return idx, true
}

// --- Surface descriptors ---

Surface_Kind :: enum {
	None,
	X11,
	Wayland,
	Win32,
}

X11_Surface_Handle :: struct {
	display: rawptr,
	window:  u64,
}

Wayland_Surface_Handle :: struct {
	display: rawptr,
	surface: rawptr,
}

Win32_Surface_Handle :: struct {
	hwnd:      rawptr,
	hinstance: rawptr,
}

Surface_Desc :: struct {
	kind:    Surface_Kind,
	x11:     X11_Surface_Handle,
	wayland: Wayland_Surface_Handle,
	win32:   Win32_Surface_Handle,
}

// --- Enums ---

Buffer_Usage :: enum {
	Vertex,
	Index,
	Uniform,
	Storage,
	Indirect_Argument,
	Transfer_Src,
	Transfer_Dst,
}
Buffer_Usage_Flags :: bit_set[Buffer_Usage]

Indirect_Draw_Args :: struct {
	vertex_count:   u32,
	instance_count: u32,
	first_vertex:   u32,
	first_instance: u32,
}

Indirect_Draw_Indexed_Args :: struct {
	index_count:    u32,
	instance_count: u32,
	first_index:    u32,
	vertex_offset:  i32,
	first_instance: u32,
}

#assert(size_of(Indirect_Draw_Args) == 16)
#assert(offset_of(Indirect_Draw_Args, vertex_count) == 0)
#assert(offset_of(Indirect_Draw_Args, instance_count) == 4)
#assert(offset_of(Indirect_Draw_Args, first_vertex) == 8)
#assert(offset_of(Indirect_Draw_Args, first_instance) == 12)
#assert(size_of(Indirect_Draw_Indexed_Args) == 20)
#assert(offset_of(Indirect_Draw_Indexed_Args, index_count) == 0)
#assert(offset_of(Indirect_Draw_Indexed_Args, instance_count) == 4)
#assert(offset_of(Indirect_Draw_Indexed_Args, first_index) == 8)
#assert(offset_of(Indirect_Draw_Indexed_Args, vertex_offset) == 12)
#assert(offset_of(Indirect_Draw_Indexed_Args, first_instance) == 16)

Memory_Property :: enum {
	Device_Local,
	Host_Visible,
	Host_Coherent,
}
Memory_Property_Flags :: bit_set[Memory_Property]

Shader_Stage :: enum {
	Vertex,
	Fragment,
	Compute,
}

Shader_Format :: enum {
	SPIRV,
	HLSL,
	GLSL,
}

Shader_Module_Desc :: struct {
	stage:  Shader_Stage,
	format: Shader_Format,
	name:   string,
	entry:  string,
	data:   []u8,
}

Shader_Stage_Flag :: enum {
	Vertex,
	Fragment,
	Compute,
}
Shader_Stage_Flags :: bit_set[Shader_Stage_Flag]

Topology :: enum {
	Triangle_List,
	Triangle_Strip,
	Line_List,
	Point_List,
}

Cull_Mode :: enum {
	None,
	Front,
	Back,
	Front_And_Back,
}

Front_Face :: enum {
	Counter_Clockwise,
	Clockwise,
}

Format :: enum {
	Undefined,
	R8G8B8A8_SRGB,
	R8G8B8A8_UNORM,
	B8G8R8A8_SRGB,
	D32_SFLOAT,
	D32_SFLOAT_S8_UINT,
	D24_UNORM_S8_UINT,
}

Image_Usage :: enum {
	Sampled,
	Color_Attachment,
	Depth_Stencil_Attachment,
	Transfer_Dst,
	Transfer_Src,
}
Image_Usage_Flags :: bit_set[Image_Usage]

Image_Aspect :: enum {
	Color,
	Depth,
	Stencil,
}
Image_Aspect_Flags :: bit_set[Image_Aspect]

Descriptor_Type :: enum {
	Combined_Image_Sampler,
	Uniform_Buffer,
	Storage_Buffer,
}

Filter :: enum {
	Nearest,
	Linear,
}

Address_Mode :: enum {
	Repeat,
	Clamp_To_Edge,
	Clamp_To_Border,
}

Compare_Op :: enum {
	Never,
	Less,
	Less_Or_Equal,
	Equal,
	Greater_Or_Equal,
	Greater,
	Not_Equal,
	Always,
}

Stencil_Op :: enum {
	Keep,
	Zero,
	Replace,
	Increment_Clamp,
	Decrement_Clamp,
	Invert,
}

Stencil_Face_State :: struct {
	fail_op:       Stencil_Op,
	pass_op:       Stencil_Op,
	depth_fail_op: Stencil_Op,
	compare_op:    Compare_Op,
}

Stencil_State :: struct {
	enable:     bool,
	read_mask:  u8,
	write_mask: u8,
	reference:  u8,
	front:      Stencil_Face_State,
	back:       Stencil_Face_State,
}

Image_Layout :: enum {
	Undefined,
	Color_Attachment,
	Depth_Stencil_Attachment,
	Shader_Read_Only,
	Depth_Stencil_Read_Only,
	Transfer_Dst,
	Present_Src,
}

Attachment_Load_Op :: enum {
	Load,
	Clear,
	Dont_Care,
}

Attachment_Store_Op :: enum {
	Store,
	Dont_Care,
}

// --- Descriptor structs ---

Vertex_Attribute :: struct {
	location: u32,
	binding:  u32,
	offset:   u32,
	format:   Vertex_Format,
}

Vertex_Format :: enum {
	Float2,
	Float3,
	Float4,
}

Vertex_Binding :: struct {
	binding:    u32,
	stride:     u32,
	input_rate: Vertex_Input_Rate,
}

Vertex_Input_Rate :: enum {
	Vertex,
	Instance,
}

// --- Vertex layout descriptor ---

Vertex_Attrib :: enum u8 {
	Position,
	Normal,
	Tex_Coord,
	Tex_Coord_1,
	Color,
	Tangent,
}

MAX_VERTEX_ATTRIBS :: 8

Vertex_Layout_Entry :: struct {
	attrib: Vertex_Attrib,
	format: Vertex_Format,
	offset: u32,
}

Vertex_Layout :: struct {
	entries: [MAX_VERTEX_ATTRIBS]Vertex_Layout_Entry,
	count:   u8,
	stride:  u32,
}

Blend_Mode :: enum u8 {
	Alpha, // src*α + dst*(1-α)
	Additive, // src*α + dst
	Premultiplied_Alpha, // src + dst*(1-α)
}

Blend_Factor :: enum {
	Zero,
	One,
	Src_Alpha,
	One_Minus_Src_Alpha,
}

Blend_Factors :: struct {
	src_color: Blend_Factor,
	dst_color: Blend_Factor,
	src_alpha: Blend_Factor,
	dst_alpha: Blend_Factor,
}

blend_factors :: proc(mode: Blend_Mode) -> Blend_Factors {
	switch mode {
	case .Alpha:
		return {.Src_Alpha, .One_Minus_Src_Alpha, .One, .One_Minus_Src_Alpha}
	case .Additive:
		return {.Src_Alpha, .One, .One, .One}
	case .Premultiplied_Alpha:
		return {.One, .One_Minus_Src_Alpha, .One, .One_Minus_Src_Alpha}
	}
	return {.Src_Alpha, .One_Minus_Src_Alpha, .One, .One_Minus_Src_Alpha}
}

Pipeline_Desc :: struct {
	vert_shader:          Shader_Handle,
	frag_shader:          Shader_Handle,
	render_pass:          Render_Pass_Handle,
	topology:             Topology,
	cull_mode:            Cull_Mode,
	front_face:           Front_Face,
	enable_blending:      bool,
	blend_mode:           Blend_Mode,
	enable_depth_test:    bool,
	enable_depth_bias:    bool,
	depth_format:         Format,
	stencil:              Stencil_State,
	depth_only:           bool,
	color_attachment_count: u32,
	color_formats:        [MAX_COLOR_TARGETS]Format,
	color_write_masks:    [MAX_COLOR_TARGETS]u8,
	push_constant_size:   u32,
	push_constant_stages: Shader_Stage_Flags,
	descriptor_layouts:   []Descriptor_Handle,
	vertex_bindings:      []Vertex_Binding,
	vertex_attributes:    []Vertex_Attribute,
}

Buffer_Desc :: struct {
	size:   u64,
	usage:  Buffer_Usage_Flags,
	memory: Memory_Property_Flags,
}

Texture_Desc :: struct {
	width:  u32,
	height: u32,
	format: Format,
	usage:  Image_Usage_Flags,
}

Readback_Texture_Desc :: struct {
	texture:        Texture_Handle,
	width, height:  u32,
	current_layout: Image_Layout,
}

Sampler_Desc :: struct {
	mag_filter:     Filter,
	min_filter:     Filter,
	address_mode_u: Address_Mode,
	address_mode_v: Address_Mode,
	enable_aniso:   bool,
	enable_compare: bool,
	compare_op:     Compare_Op,
	mipmap_mode:    Filter,
}

Descriptor_Set_Layout_Binding :: struct {
	binding: u32,
	type:    Descriptor_Type,
	count:   u32,
	stages:  Shader_Stage_Flags,
}

Render_Pass_Desc :: struct {
	has_color:          bool,
	has_depth:          bool,
	color_count:        u32,
	color_formats:      [MAX_COLOR_TARGETS]Format,
	color_load_ops:     [MAX_COLOR_TARGETS]Attachment_Load_Op,
	color_store_ops:    [MAX_COLOR_TARGETS]Attachment_Store_Op,
	color_final_layouts: [MAX_COLOR_TARGETS]Image_Layout,
	color_format:       Format,
	depth_format:       Format,
	depth_only:         bool, // depth-only pass (shadows)
	color_load_op:      Attachment_Load_Op,
	color_store_op:     Attachment_Store_Op,
	color_final_layout: Image_Layout,
	depth_load_op:      Attachment_Load_Op,
	depth_store_op:     Attachment_Store_Op,
	depth_final_layout: Image_Layout,
	has_stencil:        bool,
	stencil_load_op:    Attachment_Load_Op,
	stencil_store_op:   Attachment_Store_Op,
}

Framebuffer_Desc :: struct {
	pass:       Render_Pass_Handle,
	color_count: u32,
	color_views: [MAX_COLOR_TARGETS]Texture_Handle,
	color_view: Texture_Handle,
	depth_view: Texture_Handle,
	width:      u32,
	height:     u32,
	layers:     u32,
}

Render_Pass_Begin_Desc :: struct {
	pass:        Render_Pass_Handle,
	framebuffer: Framebuffer_Handle,
	width:       u32,
	height:      u32,
	color_count: u32,
	clear_colors: [MAX_COLOR_TARGETS][4]f32,
	clear_color: [4]f32,
	clear_depth: f32,
	clear_stencil: u8,
}

// --- Frame context ---

// Opaque handle representing the current frame's recording state.
// Backends map this to their command buffer / frame index internals.
Frame_Context :: struct {
	frame_index: u32,
	_opaque:     [2]u64, // backend-specific data (e.g. command buffer handle)
}

// --- Surface extent ---

Extent :: struct {
	width:  u32,
	height: u32,
}

// --- Backend vtable ---

// Backend is a struct of proc pointers that abstract GPU operations.
// Each backend implementation (Vulkan, D3D11, D3D12) populates these.
Backend :: struct {
	capabilities:                  Capabilities,

	// --- Lifecycle ---
	shutdown:                      proc(),
	wait_idle:                     proc(),

	// --- Frame ---
	begin_frame:                   proc(clear_color: [4]f32) -> (Frame_Context, bool),
	end_frame:                     proc(ctx: Frame_Context) -> bool, // returns true if swapchain recreated
	on_resize:                     proc(width, height: u32),
	get_extent:                    proc() -> Extent,
	current_frame_index:           proc() -> u32,

	// --- Render pass ---
	begin_render_pass:             proc(ctx: Frame_Context, desc: Render_Pass_Begin_Desc),
	begin_default_pass:            proc(ctx: Frame_Context, clear_color: [4]f32),
	end_render_pass:               proc(ctx: Frame_Context),
	set_viewport:                  proc(ctx: Frame_Context, x, y, w, h: f32),
	set_scissor:                   proc(ctx: Frame_Context, x, y: i32, w, h: u32),
	set_depth_bias:                proc(ctx: Frame_Context, constant, slope: f32),

	// --- Pipeline ---
	create_graphics_pipeline:      proc(desc: Pipeline_Desc) -> (Pipeline_Handle, bool),
	destroy_graphics_pipeline:     proc(handle: Pipeline_Handle),
	bind_graphics_pipeline:        proc(ctx: Frame_Context, handle: Pipeline_Handle),
	push_constants:                proc(
		ctx: Frame_Context,
		pipeline: Pipeline_Handle,
		stages: Shader_Stage_Flags,
		offset, size: u32,
		data: rawptr,
	),

	// --- Buffers ---
	create_buffer:                 proc(desc: Buffer_Desc) -> (Buffer_Handle, bool),
	create_buffer_staged:          proc(
		data: rawptr,
		size: int,
		usage: Buffer_Usage_Flags,
	) -> (
		Buffer_Handle,
		bool,
	),
	destroy_buffer:                proc(handle: Buffer_Handle),
	map_buffer:                    proc(handle: Buffer_Handle) -> rawptr,
	unmap_buffer:                  proc(handle: Buffer_Handle),
	get_buffer_mapped:             proc(handle: Buffer_Handle) -> rawptr,
	bind_vertex_buffer:            proc(ctx: Frame_Context, handle: Buffer_Handle),
	bind_vertex_buffer_slot:       proc(
		ctx: Frame_Context,
		slot: u32,
		handle: Buffer_Handle,
		offset: u64,
		stride: u32,
	),
	bind_index_buffer:             proc(ctx: Frame_Context, handle: Buffer_Handle),

	// --- Textures ---
	create_texture:                proc(
		desc: Texture_Desc,
		pixels: rawptr,
	) -> (
		Texture_Handle,
		bool,
	),
	destroy_texture:               proc(handle: Texture_Handle),
	read_texture_rgba8:            proc(desc: Readback_Texture_Desc, out: []u8) -> bool,

	// --- Samplers ---
	create_sampler:                proc(desc: Sampler_Desc) -> (Sampler_Handle, bool),
	destroy_sampler:               proc(handle: Sampler_Handle),

	// --- Images (for depth/shadow targets) ---
	create_image:                  proc(desc: Texture_Desc) -> (Texture_Handle, bool),
	create_image_view:             proc(
		texture: Texture_Handle,
		format: Format,
		aspect: Image_Aspect_Flags,
	) -> bool,
	destroy_image:                 proc(handle: Texture_Handle),

	// --- Descriptors ---
	create_descriptor_set_layout:  proc(
		bindings: []Descriptor_Set_Layout_Binding,
	) -> (
		Descriptor_Handle,
		bool,
	),
	destroy_descriptor_set_layout: proc(handle: Descriptor_Handle),
	create_descriptor_pool:        proc(
		max_sets: u32,
		types: []Descriptor_Type,
		counts: []u32,
	) -> (
		Descriptor_Handle,
		bool,
	),
	destroy_descriptor_pool:       proc(handle: Descriptor_Handle),
	allocate_descriptor_set:       proc(
		pool: Descriptor_Handle,
		layout: Descriptor_Handle,
	) -> (
		Descriptor_Handle,
		bool,
	),
	bind_descriptor_set:           proc(
		ctx: Frame_Context,
		pipeline: Pipeline_Handle,
		set: Descriptor_Handle,
		index: u32,
	),
	update_descriptor_image:       proc(
		set: Descriptor_Handle,
		binding: u32,
		texture: Texture_Handle,
		sampler: Sampler_Handle,
		layout: Image_Layout,
	),
	update_descriptor_buffer:      proc(
		set: Descriptor_Handle,
		binding: u32,
		buffer: Buffer_Handle,
		size: u64,
	),

	// --- Render pass objects ---
	create_render_pass:            proc(desc: Render_Pass_Desc) -> (Render_Pass_Handle, bool),
	destroy_render_pass:           proc(handle: Render_Pass_Handle),
	create_framebuffer:            proc(desc: Framebuffer_Desc) -> (Framebuffer_Handle, bool),
	destroy_framebuffer:           proc(handle: Framebuffer_Handle),

	// --- Shaders ---
	create_shader_module:          proc(desc: Shader_Module_Desc) -> (Shader_Handle, bool),
	destroy_shader:                proc(handle: Shader_Handle),

	// --- Draw ---
	draw:                          proc(
		ctx: Frame_Context,
		vertex_count, instance_count: u32,
		first_vertex: u32,
		first_instance: u32,
	),
	draw_indexed:                  proc(
		ctx: Frame_Context,
		index_count, instance_count: u32,
		first_index: u32,
		vertex_offset: i32,
		first_instance: u32,
	),
	draw_indirect:                 proc(
		ctx: Frame_Context,
		argument_buffer: Buffer_Handle,
		argument_offset: u64,
		draw_count: u32,
		stride: u32,
	),
	draw_indexed_indirect:         proc(
		ctx: Frame_Context,
		argument_buffer: Buffer_Handle,
		argument_offset: u64,
		draw_count: u32,
		stride: u32,
	),

	// --- Compute ---
	create_compute_pipeline:       proc(
		shader: Shader_Handle,
		num_buffers: u32,
		push_constant_size: u32,
	) -> (
		Pipeline_Handle,
		bool,
	),
	destroy_compute_pipeline:      proc(handle: Pipeline_Handle),
	bind_compute_pipeline:         proc(ctx: Frame_Context, handle: Pipeline_Handle),
	dispatch_compute:              proc(ctx: Frame_Context, groups_x, groups_y, groups_z: u32),
	compute_barrier:               proc(ctx: Frame_Context),

	// --- Sync ---
	get_default_render_pass:       proc() -> Render_Pass_Handle,
	get_depth_format:              proc() -> Format,
}

backend_vtable_is_complete :: proc(b: ^Backend) -> bool {
	if b == nil do return false
	return(
		b.shutdown != nil &&
		b.wait_idle != nil &&
		b.begin_frame != nil &&
		b.end_frame != nil &&
		b.on_resize != nil &&
		b.get_extent != nil &&
		b.current_frame_index != nil &&
		b.begin_render_pass != nil &&
		b.begin_default_pass != nil &&
		b.end_render_pass != nil &&
		b.set_viewport != nil &&
		b.set_scissor != nil &&
		b.set_depth_bias != nil &&
		b.create_graphics_pipeline != nil &&
		b.destroy_graphics_pipeline != nil &&
		b.bind_graphics_pipeline != nil &&
		b.push_constants != nil &&
		b.create_buffer != nil &&
		b.create_buffer_staged != nil &&
		b.destroy_buffer != nil &&
		b.map_buffer != nil &&
		b.unmap_buffer != nil &&
		b.get_buffer_mapped != nil &&
		b.bind_vertex_buffer != nil &&
		b.bind_vertex_buffer_slot != nil &&
		b.bind_index_buffer != nil &&
		b.create_texture != nil &&
		b.destroy_texture != nil &&
		b.create_sampler != nil &&
		b.destroy_sampler != nil &&
		b.create_image != nil &&
		b.create_image_view != nil &&
		b.destroy_image != nil &&
		b.create_descriptor_set_layout != nil &&
		b.destroy_descriptor_set_layout != nil &&
		b.create_descriptor_pool != nil &&
		b.destroy_descriptor_pool != nil &&
		b.allocate_descriptor_set != nil &&
		b.bind_descriptor_set != nil &&
		b.update_descriptor_image != nil &&
		b.update_descriptor_buffer != nil &&
		b.create_render_pass != nil &&
		b.destroy_render_pass != nil &&
		b.create_framebuffer != nil &&
		b.destroy_framebuffer != nil &&
		b.create_shader_module != nil &&
		b.destroy_shader != nil &&
		b.draw != nil &&
		b.draw_indexed != nil &&
		b.draw_indirect != nil &&
		b.draw_indexed_indirect != nil &&
		b.create_compute_pipeline != nil &&
		b.destroy_compute_pipeline != nil &&
		b.bind_compute_pipeline != nil &&
		b.dispatch_compute != nil &&
		b.compute_barrier != nil &&
		b.get_default_render_pass != nil &&
		b.get_depth_format != nil \
	)
}

// Initialized flag
backend_initialized: bool