فهرست منبع

Half-way done with moving shader things to karl2d.odin

Karl Zylinski 6 ماه پیش
والد
کامیت
9d3f236e1b
3فایلهای تغییر یافته به همراه208 افزوده شده و 215 حذف شده
  1. 49 185
      backend_d3d11.odin
  2. 3 2
      examples/snake/snake.odin
  3. 156 28
      karl2d.odin

+ 49 - 185
backend_d3d11.odin

@@ -20,7 +20,7 @@ BACKEND_D3D11 :: Rendering_Backend {
 	shutdown = d3d11_shutdown,
 	clear = d3d11_clear,
 	present = d3d11_present,
-	draw_current_batch = d3d11_draw_current_batch,
+	draw = d3d11_draw,
 	
 	get_swapchain_width = d3d11_get_swapchain_width,
 	get_swapchain_height = d3d11_get_swapchain_height,
@@ -32,7 +32,8 @@ BACKEND_D3D11 :: Rendering_Backend {
 	load_texture = d3d11_load_texture,
 	destroy_texture = d3d11_destroy_texture,
 
-	batch_vertex = d3d11_batch_vertex,
+	load_shader = d3d11_load_shader,
+	destroy_shader = d3d11_destroy_shader,
 }
 
 @(private="file")
@@ -133,7 +134,7 @@ d3d11_init :: proc(state: rawptr, window_handle: uintptr, swapchain_width, swapc
 		CPUAccessFlags = {.WRITE},
 	}
 	ch(s.device->CreateBuffer(&vertex_buffer_desc, nil, &s.vertex_buffer_gpu))
-	s.vertex_buffer_cpu = make([]u8, VERTEX_BUFFER_MAX, allocator, loc)
+	
 
 	blend_desc := d3d11.BLEND_DESC {
 		RenderTarget = {
@@ -161,17 +162,12 @@ d3d11_init :: proc(state: rawptr, window_handle: uintptr, swapchain_width, swapc
 	}
 	s.device->CreateSamplerState(&sampler_desc, &s.sampler_state)
 
-	s.default_shader = _load_shader(string(shader_hlsl), {
-		.RG32_Float,
-		.RG32_Float,
-		.RGBA8_Norm,
-	})
+	
 
 	
 }
 
 d3d11_shutdown :: proc() {
-	_destroy_shader(s.default_shader)
 	s.sampler_state->Release()
 	s.framebuffer_view->Release()
 	s.depth_buffer_view->Release()
@@ -184,7 +180,6 @@ d3d11_shutdown :: proc() {
 	s.rasterizer_state->Release()
 	s.swapchain->Release()
 	s.blend_state->Release()
-	delete(s.vertex_buffer_cpu, s.allocator)
 
 	when ODIN_DEBUG {
 		debug: ^d3d11.IDebug
@@ -217,8 +212,6 @@ d3d11_set_view_projection_matrix :: proc(m: Mat4) {
 	s.view_proj = m
 }
 
-shader_hlsl :: #load("shader.hlsl")
-
 VERTEX_BUFFER_MAX :: 1000000
 
 TEXTURE_NONE :: Texture_Handle {}
@@ -252,19 +245,11 @@ Shader_Default_Inputs :: enum {
 	Color,
 }
 
-Shader :: struct {
+D3D11_Shader :: struct {
 	handle: Shader_Handle,
 	vertex_shader: ^d3d11.IVertexShader,
 	pixel_shader: ^d3d11.IPixelShader,
 	input_layout: ^d3d11.IInputLayout,
-	constant_buffers: []Shader_Constant_Buffer,
-	constant_lookup: map[string]Shader_Constant_Location,
-	constant_builtin_locations: [Shader_Builtin_Constant]Maybe(Shader_Constant_Location),
-
-	inputs: []Shader_Input,
-	input_overrides: []Shader_Input_Value_Override,
-	default_input_offsets: [Shader_Default_Inputs]int,
-	vertex_size: int,
 }
 
 D3D11_State :: struct {
@@ -287,12 +272,10 @@ D3D11_State :: struct {
 	default_shader: Shader_Handle,
 
 	textures: hm.Handle_Map(_Texture, Texture_Handle, 1024*10),
-	shaders: hm.Handle_Map(Shader, Shader_Handle, 1024*10),
+	shaders: hm.Handle_Map(D3D11_Shader, Shader_Handle, 1024*10),
 
 	info_queue: ^d3d11.IInfoQueue,
 	vertex_buffer_gpu: ^d3d11.IBuffer,
-	vertex_buffer_cpu: []u8,
-	vertex_buffer_cpu_used: int,
 
 	vertex_buffer_offset: int,
 	
@@ -381,67 +364,6 @@ create_vertex_input_override :: proc(val: $T) -> Shader_Input_Value_Override {
 	return res
 }
 
-set_vertex_input_override :: proc(shader: Shader_Handle, input_idx: int, override: Shader_Input_Value_Override) {
-	shd := hm.get(&s.shaders, shader)
-
-	if shd == nil {
-		log.error("Valid shader")
-		return
-	}
-
-	if input_idx < 0 || input_idx >= len(shd.input_overrides) {
-		log.error("Override input idx out of bounds")
-		return
-	}
-
-	shd.input_overrides[input_idx] = override
-}
-
-d3d11_batch_vertex :: proc(v: Vec2, uv: Vec2, color: Color) {
-	v := v
-
-	if s.vertex_buffer_cpu_used == len(s.vertex_buffer_cpu) {
-		panic("Must dispatch here")
-	}
-
-	shd := hm.get(&s.shaders, s.batch_shader)
-
-	if shd == nil {
-		shd = hm.get(&s.shaders, s.default_shader)
-		assert(shd != nil, "Failed fetching default shader")
-	}
-
-	base_offset := s.vertex_buffer_cpu_used
-	pos_offset := shd.default_input_offsets[.Position]
-	uv_offset := shd.default_input_offsets[.UV]
-	color_offset := shd.default_input_offsets[.Color]
-	
-	if pos_offset != -1 {
-		(^Vec2)(&s.vertex_buffer_cpu[base_offset + pos_offset])^ = v
-	}
-
-	if uv_offset != -1 {
-		(^Vec2)(&s.vertex_buffer_cpu[base_offset + uv_offset])^ = uv
-	}
-
-	if color_offset != -1 {
-		(^Color)(&s.vertex_buffer_cpu[base_offset + color_offset])^ = color
-	}
-
-	override_offset: int
-	for &o, idx in shd.input_overrides {
-		input := &shd.inputs[idx]
-		sz := shader_input_format_size(input.format)
-
-		if o.used != 0 {
-			mem.copy(&s.vertex_buffer_cpu[base_offset + override_offset], raw_data(&o.val), o.used)
-		}
-
-		override_offset += sz
-	}
-	
-	s.vertex_buffer_cpu_used += shd.vertex_size
-}
 
 _draw_circle :: proc(center: Vec2, radius: f32, color: Color) {
 }
@@ -498,56 +420,22 @@ _set_scissor_rect :: proc(scissor_rect: Maybe(Rect)) {
 
 }
 
-_set_shader :: proc(shader: Shader_Handle) {
-	if shader == s.batch_shader {
+d3d11_draw :: proc(shd: Shader, texture: Texture_Handle, vertex_buffer: []u8) {
+	if len(vertex_buffer) == 0 {
 		return
 	}
 
-	d3d11_draw_current_batch()
-	s.batch_shader = shader
-}
-
-_set_shader_constant :: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: $T) {
-	d3d11_draw_current_batch()
-
-	shd := hm.get(&s.shaders, shader)
-
-	if shd == nil {
-		return
-	}
+	d3d_shd := hm.get(&s.shaders, shd.handle)
 
-	if int(loc.buffer_idx) >= len(shd.constant_buffers) {
-		log.warnf("Constant buffer idx %v is out of bounds", loc.buffer_idx)
+	if d3d_shd == nil {
 		return
 	}
 
-	b := &shd.constant_buffers[loc.buffer_idx]
-
-	if int(loc.offset) + size_of(val) > len(b.cpu_data) {
-		log.warnf("Constant buffer idx %v is trying to be written out of bounds by at offset %v with %v bytes", loc.buffer_idx, loc.offset, size_of(val))
-		return
-	}
-
-	dst := (^T)(&b.cpu_data[loc.offset])
-	dst^ = val
-}
-
-_set_shader_constant_mat4 :: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: matrix[4,4]f32) {
-	_set_shader_constant(shader, loc, val)
-}
-
-_set_shader_constant_f32 :: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: f32) {
-	_set_shader_constant(shader, loc, val)
-}
-
-_set_shader_constant_vec2 :: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: Vec2) {
-	_set_shader_constant(shader, loc, val)
-}
+	/*if shd == nil {
+		shd = hm.get(&s.shaders, s.default_shader)
+		assert(shd != nil, "Failed fetching default shader")
+	}*/
 
-d3d11_draw_current_batch :: proc() {
-	if s.vertex_buffer_cpu_used == s.vertex_buffer_offset {
-		return
-	}
 
 	viewport := d3d11.VIEWPORT{
 		0, 0,
@@ -562,22 +450,16 @@ d3d11_draw_current_batch :: proc() {
 	{
 		gpu_map := slice.from_ptr((^u8)(vb_data.pData), VERTEX_BUFFER_MAX)
 		copy(
-			gpu_map[s.vertex_buffer_offset:s.vertex_buffer_cpu_used],
-			s.vertex_buffer_cpu[s.vertex_buffer_offset:s.vertex_buffer_cpu_used],
+			gpu_map[s.vertex_buffer_offset:s.vertex_buffer_offset+len(vertex_buffer)],
+			vertex_buffer,
 		)
 	}
 	dc->Unmap(s.vertex_buffer_gpu, 0)
 
 
 	dc->IASetPrimitiveTopology(.TRIANGLELIST)
-	shd := hm.get(&s.shaders, s.batch_shader)
 
-	if shd == nil {
-		shd = hm.get(&s.shaders, s.default_shader)
-		assert(shd != nil, "Failed fetching default shader")
-	}
-
-	dc->IASetInputLayout(shd.input_layout)
+	dc->IASetInputLayout(d3d_shd.input_layout)
 	vertex_buffer_offset := u32(0)
 	vertex_buffer_stride := u32(shd.vertex_size)
 	dc->IASetVertexBuffers(0, 1, &s.vertex_buffer_gpu, &vertex_buffer_stride, &vertex_buffer_offset)
@@ -596,7 +478,7 @@ d3d11_draw_current_batch :: proc() {
 		}
 	}
 
-	dc->VSSetShader(shd.vertex_shader, nil, 0)
+	dc->VSSetShader(d3d_shd.vertex_shader, nil, 0)
 
 	for &c, c_idx in shd.constant_buffers {
 		if c.gpu_data == nil {
@@ -614,9 +496,9 @@ d3d11_draw_current_batch :: proc() {
 	dc->RSSetViewports(1, &viewport)
 	dc->RSSetState(s.rasterizer_state)
 
-	dc->PSSetShader(shd.pixel_shader, nil, 0)
+	dc->PSSetShader(d3d_shd.pixel_shader, nil, 0)
 
-	if t := hm.get(&s.textures, batch_texture); t != nil {
+	if t := hm.get(&s.textures, texture); t != nil {
 		dc->PSSetShaderResources(0, 1, &t.view)	
 	}
 	
@@ -626,8 +508,8 @@ d3d11_draw_current_batch :: proc() {
 	dc->OMSetDepthStencilState(s.depth_stencil_state, 0)
 	dc->OMSetBlendState(s.blend_state, nil, ~u32(0))
 
-	dc->Draw(u32((s.vertex_buffer_cpu_used - s.vertex_buffer_offset)/shd.vertex_size), u32(s.vertex_buffer_offset/shd.vertex_size))
-	s.vertex_buffer_offset = s.vertex_buffer_cpu_used
+	dc->Draw(u32(len(vertex_buffer)/shd.vertex_size), u32(s.vertex_buffer_offset/shd.vertex_size))
+	s.vertex_buffer_offset += len(vertex_buffer)
 	log_messages()
 }
 
@@ -635,18 +517,9 @@ make_default_projection :: proc(w, h: int) -> matrix[4,4]f32 {
 	return linalg.matrix_ortho3d_f32(0, f32(w), f32(h), 0, 0.001, 2)
 }
 
-@(private="file")
-batch_texture: Texture_Handle
-
-_tmp_set_batch_texture  :: proc(t: Texture_Handle) {
-	batch_texture = t
-}
-
 d3d11_present :: proc() {
-	d3d11_draw_current_batch()
 	ch(s.swapchain->Present(1, {}))
 	s.vertex_buffer_offset = 0
-	s.vertex_buffer_cpu_used = 0
 }
 
 Shader_Constant_Location :: struct {
@@ -654,7 +527,7 @@ Shader_Constant_Location :: struct {
 	offset: u32,
 }
 
-_load_shader :: proc(shader: string, layout_formats: []Shader_Input_Format = {}) -> Shader_Handle {
+d3d11_load_shader :: proc(shader: string, layout_formats: []Shader_Input_Format = {}) -> Shader {
 	vs_blob: ^d3d11.IBlob
 	vs_blob_errors: ^d3d11.IBlob
 	ch(d3d_compiler.Compile(raw_data(shader), len(shader), nil, nil, nil, "vs_main", "vs_5_0", 0, 0, &vs_blob, &vs_blob_errors))
@@ -857,10 +730,15 @@ _load_shader :: proc(shader: string, layout_formats: []Shader_Input_Format = {})
 	pixel_shader: ^d3d11.IPixelShader
 	ch(s.device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), nil, &pixel_shader))
 
-	shd := Shader {
+	shd := D3D11_Shader {
 		vertex_shader = vertex_shader,
 		pixel_shader = pixel_shader,
 		input_layout = input_layout,
+	}
+
+	h := hm.add(&s.shaders, shd)
+	return {
+		handle = h,
 		constant_buffers = constant_buffers,
 		constant_lookup = constant_lookup,
 		constant_builtin_locations = constant_builtin_locations,
@@ -869,9 +747,6 @@ _load_shader :: proc(shader: string, layout_formats: []Shader_Input_Format = {})
 		default_input_offsets = default_input_offsets,
 		vertex_size = input_offset,
 	}
-
-	h := hm.add(&s.shaders, shd)
-	return h
 }
 
 dxgi_format_from_shader_input_format :: proc(f: Shader_Input_Format) -> dxgi.FORMAT {
@@ -901,44 +776,33 @@ shader_input_format_size :: proc(f: Shader_Input_Format) -> int {
 	return 0
 }
 
-_destroy_shader :: proc(shader: Shader_Handle) {
-	if shd := hm.get(&s.shaders, shader); shd != nil {
-		shd.input_layout->Release()
-		shd.vertex_shader->Release()
-		shd.pixel_shader->Release()
-
-		for c in shd.constant_buffers {
-			if c.gpu_data != nil {
-				c.gpu_data->Release()
-			}
-
-			delete(c.cpu_data)
-		}
-
-		delete(shd.constant_buffers)
+d3d11_destroy_shader :: proc(shd: Shader) {
+	if d3d_shd := hm.get(&s.shaders, shd.handle); d3d_shd != nil {
+		d3d_shd.input_layout->Release()
+		d3d_shd.vertex_shader->Release()
+		d3d_shd.pixel_shader->Release()
+	}
+	hm.remove(&s.shaders, shd.handle)
 
-		for k,_ in shd.constant_lookup {
-			delete(k)
+	for c in shd.constant_buffers {
+		if c.gpu_data != nil {
+			c.gpu_data->Release()
 		}
 
-		delete(shd.constant_lookup)
-		for i in shd.inputs {
-			delete(i.name)
-		}
-		delete(shd.inputs)
+		delete(c.cpu_data)
 	}
 
-	hm.remove(&s.shaders, shader)
-}
-
-_get_shader_constant_location :: proc(shader: Shader_Handle, name: string) -> Shader_Constant_Location {
-	shd := hm.get(&s.shaders, shader)
+	delete(shd.constant_buffers)
 
-	if shd == nil {
-		return {}
+	for k,_ in shd.constant_lookup {
+		delete(k)
 	}
 
-	return shd.constant_lookup[name]
+	delete(shd.constant_lookup)
+	for i in shd.inputs {
+		delete(i.name)
+	}
+	delete(shd.inputs)
 }
 
 temp_cstring :: proc(str: string, loc := #caller_location) -> cstring {

+ 3 - 2
examples/snake/snake.odin

@@ -96,6 +96,7 @@ main :: proc() {
 	head_sprite := k2.load_texture_from_file("head.png")
 	body_sprite := k2.load_texture_from_file("body.png")
 	tail_sprite := k2.load_texture_from_file("tail.png")
+
 	food_eaten_at := time.now()
 	started_at := time.now()
 
@@ -175,7 +176,7 @@ main :: proc() {
 		time_since_food := time.duration_seconds(time.diff(food_eaten_at, time_now))
 
 		if time_since_food < 0.5 && total_time > 1 {
-			k2.set_vertex_input_override(shader, 3, k2.create_vertex_input_override(k2.Vec2{f32(math.cos(total_time*100)), f32(math.sin(total_time*120 + 3))}))
+			shader.input_overrides[3] = k2.create_vertex_input_override(k2.Vec2{f32(math.cos(total_time*100)), f32(math.sin(total_time*120 + 3))})
 		}
 
 		for i in 0..<snake_length {
@@ -214,7 +215,7 @@ main :: proc() {
 			k2.draw_text("Press Enter to play again", {4, 30}, 15, k2.BLACK)
 		}
 
-		k2.set_vertex_input_override(shader, 3, {})
+		shader.input_overrides[3] = {}
 
 		score := snake_length - 3
 		score_str := fmt.tprintf("Score: %v", score)

+ 156 - 28
karl2d.odin

@@ -96,16 +96,28 @@ init :: proc(window_width: int, window_height: int, window_title: string,
 	s.proj_matrix = make_default_projection(window_width, window_height)
 	s.view_matrix = 1
 	s.rb.init(s.rb_state, uintptr(hwnd), window_width, window_height, allocator, loc)
+	s.vertex_buffer_cpu = make([]u8, VERTEX_BUFFER_MAX, allocator, loc)
 	white_rect: [16*16*4]u8
 	slice.fill(white_rect[:], 255)
 	s.shape_drawing_texture = s.rb.load_texture(white_rect[:], 16, 16)
+
+	s.default_shader = s.rb.load_shader(string(DEFAULT_SHADER_SOURCE), {
+		.RG32_Float,
+		.RG32_Float,
+		.RGBA8_Norm,
+	})
+
 	return s
 }
 
+DEFAULT_SHADER_SOURCE :: #load("shader.hlsl")
+
 // Closes the window and cleans up the internal state.
 shutdown :: proc() {
 	s.rb.destroy_texture(s.shape_drawing_texture)
+	destroy_shader(s.default_shader)
 	s.rb.shutdown()
+	delete(s.vertex_buffer_cpu, s.allocator)
 
 	win32.DestroyWindow(s.window)
 
@@ -122,6 +134,7 @@ clear :: proc(color: Color) {
 
 // Present the backbuffer. Call at end of frame to make everything you've drawn appear on the screen.
 present :: proc() {
+	draw_current_batch()
 	s.rb.present()
 }
 
@@ -151,7 +164,9 @@ TODO: complete this list and motivate why it needs to happen on those procs (or
 docs for those procs).
 */   
 draw_current_batch :: proc() {
-	s.rb.draw_current_batch()
+	shader := s.batch_shader.? or_else s.default_shader
+	s.rb.draw(shader, s.batch_texture, s.vertex_buffer_cpu[:s.vertex_buffer_cpu_used])
+	s.vertex_buffer_cpu_used = 0
 }
 
 // Can be used to restore the internal state using the pointer returned by `init`. Useful after
@@ -209,7 +224,7 @@ set_camera :: proc(camera: Maybe(Camera)) {
 		return
 	}
 
-	s.rb.draw_current_batch()
+	draw_current_batch()
 	s.batch_camera = camera
 	s.proj_matrix = make_default_projection(s.width, s.height)
 
@@ -252,18 +267,17 @@ destroy_texture :: proc(tex: Texture) {
 
 draw_rect :: proc(r: Rect, c: Color) {
 	if s.batch_texture != TEXTURE_NONE && s.batch_texture != s.shape_drawing_texture {
-		s.rb.draw_current_batch()
+		draw_current_batch()
 	}
 
 	s.batch_texture = s.shape_drawing_texture
-	_tmp_set_batch_texture(s.batch_texture)
 
-	s.rb.batch_vertex({r.x, r.y}, {0, 0}, c)
-	s.rb.batch_vertex({r.x + r.w, r.y}, {1, 0}, c)
-	s.rb.batch_vertex({r.x + r.w, r.y + r.h}, {1, 1}, c)
-	s.rb.batch_vertex({r.x, r.y}, {0, 0}, c)
-	s.rb.batch_vertex({r.x + r.w, r.y + r.h}, {1, 1}, c)
-	s.rb.batch_vertex({r.x, r.y + r.h}, {0, 1}, c)
+	_batch_vertex({r.x, r.y}, {0, 0}, c)
+	_batch_vertex({r.x + r.w, r.y}, {1, 0}, c)
+	_batch_vertex({r.x + r.w, r.y + r.h}, {1, 1}, c)
+	_batch_vertex({r.x, r.y}, {0, 0}, c)
+	_batch_vertex({r.x + r.w, r.y + r.h}, {1, 1}, c)
+	_batch_vertex({r.x, r.y + r.h}, {0, 1}, c)
 }
 
 draw_rect_outline :: proc(r: Rect, thickness: f32, color: Color) {
@@ -333,7 +347,7 @@ draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rotati
 	}
 
 	if s.batch_texture != TEXTURE_NONE && s.batch_texture != tex.handle {
-		s.rb.draw_current_batch()
+		draw_current_batch()
 	}
 
 	r := dst
@@ -342,7 +356,6 @@ draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rotati
 	r.y -= origin.y
 
 	s.batch_texture = tex.handle
-	_tmp_set_batch_texture(tex.handle)
 	tl, tr, bl, br: Vec2
 
 	// Rotation adapted from Raylib's "DrawTexturePro"
@@ -387,12 +400,116 @@ draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rotati
 	us := Vec2{src.w, src.h} / ts
 	c := tint
 
-	s.rb.batch_vertex(tl, up, c)
-	s.rb.batch_vertex(tr, up + {us.x, 0}, c)
-	s.rb.batch_vertex(br, up + us, c)
-	s.rb.batch_vertex(tl, up, c)
-	s.rb.batch_vertex(br, up + us, c)
-	s.rb.batch_vertex(bl, up + {0, us.y}, c)
+	_batch_vertex(tl, up, c)
+	_batch_vertex(tr, up + {us.x, 0}, c)
+	_batch_vertex(br, up + us, c)
+	_batch_vertex(tl, up, c)
+	_batch_vertex(br, up + us, c)
+	_batch_vertex(bl, up + {0, us.y}, c)
+}
+
+load_shader :: proc(shader_source: string, layout_formats: []Shader_Input_Format = {}) -> Shader {
+	return s.rb.load_shader(shader_source, layout_formats)
+}
+
+destroy_shader :: proc(shader: Shader) {
+	s.rb.destroy_shader(shader)
+}
+
+set_shader :: proc(shader: Maybe(Shader)) {
+	if maybe_handle_equal(shader, s.batch_shader) {
+		return
+	}
+
+	draw_current_batch()
+	s.batch_shader = shader
+}
+
+maybe_handle_equal :: proc(m1: Maybe($T), m2: Maybe(T)) -> bool {
+	if m1 == nil && m2 == nil {
+		return true
+	}
+
+	m1v, m1v_ok := m1.?
+	m2v, m2v_ok := m2.?
+
+	if !m1v_ok || !m2v_ok {
+		return false
+	}
+
+	return m1v.handle == m2v.handle
+}
+
+set_shader_constant :: proc(shd: Shader, loc: Shader_Constant_Location, val: $T) {
+	draw_current_batch()
+
+	if int(loc.buffer_idx) >= len(shd.constant_buffers) {
+		log.warnf("Constant buffer idx %v is out of bounds", loc.buffer_idx)
+		return
+	}
+
+	b := &shd.constant_buffers[loc.buffer_idx]
+
+	if int(loc.offset) + size_of(val) > len(b.cpu_data) {
+		log.warnf("Constant buffer idx %v is trying to be written out of bounds by at offset %v with %v bytes", loc.buffer_idx, loc.offset, size_of(val))
+		return
+	}
+
+	dst := (^T)(&b.cpu_data[loc.offset])
+	dst^ = val
+}
+
+set_shader_constant_mat4 :: proc(shader: Shader, loc: Shader_Constant_Location, val: matrix[4,4]f32) {
+	set_shader_constant(shader, loc, val)
+}
+
+set_shader_constant_f32 :: proc(shader: Shader, loc: Shader_Constant_Location, val: f32) {
+	set_shader_constant(shader, loc, val)
+}
+
+set_shader_constant_vec2 :: proc(shader: Shader, loc: Shader_Constant_Location, val: Vec2) {
+	set_shader_constant(shader, loc, val)
+}
+
+_batch_vertex :: proc(v: Vec2, uv: Vec2, color: Color) {
+	v := v
+
+	if s.vertex_buffer_cpu_used == len(s.vertex_buffer_cpu) {
+		panic("Must dispatch here")
+	}
+
+	shd := s.batch_shader.? or_else s.default_shader
+
+	base_offset := s.vertex_buffer_cpu_used
+	pos_offset := shd.default_input_offsets[.Position]
+	uv_offset := shd.default_input_offsets[.UV]
+	color_offset := shd.default_input_offsets[.Color]
+	
+	if pos_offset != -1 {
+		(^Vec2)(&s.vertex_buffer_cpu[base_offset + pos_offset])^ = v
+	}
+
+	if uv_offset != -1 {
+		(^Vec2)(&s.vertex_buffer_cpu[base_offset + uv_offset])^ = uv
+	}
+
+	if color_offset != -1 {
+		(^Color)(&s.vertex_buffer_cpu[base_offset + color_offset])^ = color
+	}
+
+	override_offset: int
+	for &o, idx in shd.input_overrides {
+		input := &shd.inputs[idx]
+		sz := shader_input_format_size(input.format)
+
+		if o.used != 0 {
+			mem.copy(&s.vertex_buffer_cpu[base_offset + override_offset], raw_data(&o.val), o.used)
+		}
+
+		override_offset += sz
+	}
+	
+	s.vertex_buffer_cpu_used += shd.vertex_size
 }
 
 Rendering_Backend :: struct {
@@ -402,12 +519,15 @@ Rendering_Backend :: struct {
 	shutdown: proc(),
 	clear: proc(color: Color),
 	present: proc(),
-	draw_current_batch: proc(),
+	draw: proc(shader: Shader, texture: Texture_Handle, vertex_buffer: []u8),
 	set_internal_state: proc(state: rawptr),
 
 	load_texture: proc(data: []u8, width: int, height: int) -> Texture_Handle,
 	destroy_texture: proc(handle: Texture_Handle),
 
+	load_shader: proc(shader: string, layout_formats: []Shader_Input_Format = {}) -> Shader,
+	destroy_shader: proc(shader: Shader),
+
 	get_swapchain_width: proc() -> int,
 	get_swapchain_height: proc() -> int,
 
@@ -434,10 +554,15 @@ State :: struct {
 
 	shape_drawing_texture: Texture_Handle,
 	batch_camera: Maybe(Camera),
+	batch_shader: Maybe(Shader),
 	batch_texture: Texture_Handle,
 
 	view_matrix: Mat4,
 	proj_matrix: Mat4,
+
+	vertex_buffer_cpu: []u8,
+	vertex_buffer_cpu_used: int,
+	default_shader: Shader,
 }
 
 VK_MAP := [255]Keyboard_Key {
@@ -491,21 +616,12 @@ get_default_shader: proc() -> Shader_Handle : _get_default_shader
 
 
 set_scissor_rect: proc(scissor_rect: Maybe(Rect)) : _set_scissor_rect
-set_shader: proc(shader: Shader_Handle) : _set_shader
 
 //set_vertex_value :: _set_vertex_value
 
 draw_circle: proc(center: Vec2, radius: f32, color: Color) : _draw_circle
 draw_line: proc(start: Vec2, end: Vec2, thickness: f32, color: Color) : _draw_line
 
-load_shader: proc(shader_source: string, layout_formats: []Shader_Input_Format = {}) -> Shader_Handle : _load_shader
-destroy_shader: proc(shader: Shader_Handle) : _destroy_shader
-
-get_shader_constant_location: proc(shader: Shader_Handle, name: string) -> Shader_Constant_Location : _get_shader_constant_location
-set_shader_constant :: _set_shader_constant
-set_shader_constant_mat4: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: matrix[4,4]f32) : _set_shader_constant_mat4
-set_shader_constant_f32: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: f32) : _set_shader_constant_f32
-set_shader_constant_vec2: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: Vec2) : _set_shader_constant_vec2
 
 Shader_Input_Format :: enum {
 	Unknown,
@@ -548,6 +664,18 @@ Texture :: struct {
 	height: int,
 }
 
+Shader :: struct {
+	handle: Shader_Handle,
+	constant_buffers: []Shader_Constant_Buffer,
+	constant_lookup: map[string]Shader_Constant_Location,
+	constant_builtin_locations: [Shader_Builtin_Constant]Maybe(Shader_Constant_Location),
+
+	inputs: []Shader_Input,
+	input_overrides: []Shader_Input_Value_Override,
+	default_input_offsets: [Shader_Default_Inputs]int,
+	vertex_size: int,
+}
+
 Camera :: struct {
 	target: Vec2,
 	origin: Vec2,