#+build windows package karl2d import "base:runtime" import win32 "core:sys/windows" import d3d11 "vendor:directx/d3d11" import dxgi "vendor:directx/dxgi" import "vendor:directx/d3d_compiler" import "core:strings" import "core:log" import "core:math/linalg" import "core:slice" import "core:mem" import "core:math" import "core:image" import hm "handle_map" import "core:image/bmp" import "core:image/png" import "core:image/tga" _ :: bmp _ :: png _ :: tga _init :: proc(width: int, height: int, title: string, allocator := context.allocator, loc := #caller_location) -> ^State { win32.SetProcessDPIAware() s = new(State, allocator, loc) s.allocator = allocator s.custom_context = context CLASS_NAME :: "karl2d" instance := win32.HINSTANCE(win32.GetModuleHandleW(nil)) s.run = true s.width = width s.height = height cls := win32.WNDCLASSW { lpfnWndProc = window_proc, lpszClassName = CLASS_NAME, hInstance = instance, hCursor = win32.LoadCursorA(nil, win32.IDC_ARROW), } win32.RegisterClassW(&cls) r: win32.RECT r.right = i32(width) r.bottom = i32(height) style := win32.WS_OVERLAPPEDWINDOW | win32.WS_VISIBLE win32.AdjustWindowRect(&r, style, false) hwnd := win32.CreateWindowW(CLASS_NAME, win32.utf8_to_wstring(title), style, 100, 10, r.right - r.left, r.bottom - r.top, nil, nil, instance, nil) s.window = hwnd assert(hwnd != nil, "Failed creating window") feature_levels := [?]d3d11.FEATURE_LEVEL{ ._11_1, ._11_0, } base_device: ^d3d11.IDevice base_device_context: ^d3d11.IDeviceContext device_flags := d3d11.CREATE_DEVICE_FLAGS { .BGRA_SUPPORT, } when ODIN_DEBUG { device_flags += { .DEBUG } } ch(d3d11.CreateDevice( nil, .HARDWARE, nil, device_flags, &feature_levels[0], len(feature_levels), d3d11.SDK_VERSION, &base_device, nil, &base_device_context)) ch(base_device->QueryInterface(d3d11.IInfoQueue_UUID, (^rawptr)(&s.info_queue))) ch(base_device->QueryInterface(d3d11.IDevice_UUID, (^rawptr)(&s.device))) ch(base_device_context->QueryInterface(d3d11.IDeviceContext_UUID, (^rawptr)(&s.device_context))) dxgi_device: ^dxgi.IDevice ch(s.device->QueryInterface(dxgi.IDevice_UUID, (^rawptr)(&dxgi_device))) base_device->Release() base_device_context->Release() dxgi_adapter: ^dxgi.IAdapter ch(dxgi_device->GetAdapter(&dxgi_adapter)) dxgi_device->Release() dxgi_factory: ^dxgi.IFactory2 ch(dxgi_adapter->GetParent(dxgi.IFactory2_UUID, (^rawptr)(&dxgi_factory))) swapchain_desc := dxgi.SWAP_CHAIN_DESC1 { Format = .B8G8R8A8_UNORM, SampleDesc = { Count = 1, }, BufferUsage = {.RENDER_TARGET_OUTPUT}, BufferCount = 2, Scaling = .STRETCH, SwapEffect = .DISCARD, } ch(dxgi_factory->CreateSwapChainForHwnd(s.device, hwnd, &swapchain_desc, nil, nil, &s.swapchain)) ch(s.swapchain->GetBuffer(0, d3d11.ITexture2D_UUID, (^rawptr)(&s.framebuffer))) ch(s.device->CreateRenderTargetView(s.framebuffer, nil, &s.framebuffer_view)) depth_buffer_desc: d3d11.TEXTURE2D_DESC s.framebuffer->GetDesc(&depth_buffer_desc) depth_buffer_desc.Format = .D24_UNORM_S8_UINT depth_buffer_desc.BindFlags = {.DEPTH_STENCIL} ch(s.device->CreateTexture2D(&depth_buffer_desc, nil, &s.depth_buffer)) ch(s.device->CreateDepthStencilView(s.depth_buffer, nil, &s.depth_buffer_view)) rasterizer_desc := d3d11.RASTERIZER_DESC{ FillMode = .SOLID, CullMode = .BACK, } ch(s.device->CreateRasterizerState(&rasterizer_desc, &s.rasterizer_state)) depth_stencil_desc := d3d11.DEPTH_STENCIL_DESC{ DepthEnable = false, DepthWriteMask = .ALL, DepthFunc = .LESS, } ch(s.device->CreateDepthStencilState(&depth_stencil_desc, &s.depth_stencil_state)) vertex_buffer_desc := d3d11.BUFFER_DESC{ ByteWidth = VERTEX_BUFFER_MAX, Usage = .DYNAMIC, BindFlags = {.VERTEX_BUFFER}, 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 = { 0 = { BlendEnable = true, SrcBlend = .SRC_ALPHA, DestBlend = .INV_SRC_ALPHA, BlendOp = .ADD, SrcBlendAlpha = .ONE, DestBlendAlpha = .ZERO, BlendOpAlpha = .ADD, RenderTargetWriteMask = u8(d3d11.COLOR_WRITE_ENABLE_ALL), }, }, } ch(s.device->CreateBlendState(&blend_desc, &s.blend_state)) s.proj_matrix = make_default_projection(s.width, s.height) s.view_matrix = 1 sampler_desc := d3d11.SAMPLER_DESC{ Filter = .MIN_MAG_MIP_POINT, AddressU = .WRAP, AddressV = .WRAP, AddressW = .WRAP, ComparisonFunc = .NEVER, } s.device->CreateSamplerState(&sampler_desc, &s.sampler_state) s.default_shader = _load_shader(string(shader_hlsl)) white_rect: [16*16*4]u8 slice.fill(white_rect[:], 255) s.shape_drawing_texture = _load_texture_from_memory(white_rect[:], 16, 16) return s } shader_hlsl :: #load("shader.hlsl") s: ^State VERTEX_BUFFER_MAX :: 1000000 Handle :: hm.Handle Texture_Handle :: distinct hm.Handle TEXTURE_NONE :: Texture_Handle {} Shader_Constant_Buffer :: struct { gpu_data: ^d3d11.IBuffer, cpu_data: []u8, } Shader_Builtin_Constant :: enum { MVP, } Shader_Input_Type :: enum { F32, Vec2, Vec3, Vec4, } Shader_Input :: struct { name: string, register: int, type: Shader_Input_Type, format: Shader_Input_Format, } Shader_Default_Inputs :: enum { Position, UV, Color, } 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, default_input_offsets: [Shader_Default_Inputs]int, vertex_size: int, } State :: struct { swapchain: ^dxgi.ISwapChain1, framebuffer_view: ^d3d11.IRenderTargetView, depth_buffer_view: ^d3d11.IDepthStencilView, device_context: ^d3d11.IDeviceContext, depth_stencil_state: ^d3d11.IDepthStencilState, rasterizer_state: ^d3d11.IRasterizerState, device: ^d3d11.IDevice, depth_buffer: ^d3d11.ITexture2D, framebuffer: ^d3d11.ITexture2D, blend_state: ^d3d11.IBlendState, shape_drawing_texture: Texture, sampler_state: ^d3d11.ISamplerState, default_shader: Shader_Handle, textures: hm.Handle_Map(_Texture, Texture_Handle, 1024*10), shaders: hm.Handle_Map(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, run: bool, custom_context: runtime.Context, allocator: runtime.Allocator, batch_texture: Texture_Handle, batch_camera: Maybe(Camera), batch_shader: Shader_Handle, window: win32.HWND, width: int, height: int, keys_went_down: #sparse [Keyboard_Key]bool, keys_went_up: #sparse [Keyboard_Key]bool, keys_is_held: #sparse [Keyboard_Key]bool, view_matrix: matrix[4,4]f32, proj_matrix: matrix[4,4]f32, } VK_MAP := [255]Keyboard_Key { win32.VK_A = .A, win32.VK_B = .B, win32.VK_C = .C, win32.VK_D = .D, win32.VK_E = .E, win32.VK_F = .F, win32.VK_G = .G, win32.VK_H = .H, win32.VK_I = .I, win32.VK_J = .J, win32.VK_K = .K, win32.VK_L = .L, win32.VK_M = .M, win32.VK_N = .N, win32.VK_O = .O, win32.VK_P = .P, win32.VK_Q = .Q, win32.VK_R = .R, win32.VK_S = .S, win32.VK_T = .T, win32.VK_U = .U, win32.VK_V = .V, win32.VK_W = .W, win32.VK_X = .X, win32.VK_Y = .Y, win32.VK_Z = .Z, win32.VK_LEFT = .Left, win32.VK_RIGHT = .Right, win32.VK_UP = .Up, win32.VK_DOWN = .Down, } window_proc :: proc "stdcall" (hwnd: win32.HWND, msg: win32.UINT, wparam: win32.WPARAM, lparam: win32.LPARAM) -> win32.LRESULT { context = s.custom_context switch msg { case win32.WM_DESTROY: win32.PostQuitMessage(0) s.run = false case win32.WM_CLOSE: s.run = false case win32.WM_KEYDOWN: key := VK_MAP[wparam] s.keys_went_down[key] = true s.keys_is_held[key] = true case win32.WM_KEYUP: key := VK_MAP[wparam] s.keys_is_held[key] = false s.keys_went_up[key] = true } return win32.DefWindowProcW(hwnd, msg, wparam, lparam) } _shutdown :: proc() { _destroy_texture(s.shape_drawing_texture) _destroy_shader(s.default_shader) s.sampler_state->Release() s.framebuffer_view->Release() s.depth_buffer_view->Release() s.depth_buffer->Release() s.framebuffer->Release() s.device_context->Release() s.vertex_buffer_gpu->Release() //s.constant_buffer->Release() s.depth_stencil_state->Release() s.rasterizer_state->Release() s.swapchain->Release() s.blend_state->Release() delete(s.vertex_buffer_cpu, s.allocator) when ODIN_DEBUG { debug: ^d3d11.IDebug if ch(s.device->QueryInterface(d3d11.IDebug_UUID, (^rawptr)(&debug))) >= 0 { ch(debug->ReportLiveDeviceObjects({.DETAIL, .IGNORE_INTERNAL})) log_messages() } debug->Release() } s.device->Release() s.info_queue->Release() a := s.allocator free(s, a) s = nil } _set_internal_state :: proc(new_state: ^State) { s = new_state } Color_F32 :: [4]f32 f32_color_from_color :: proc(color: Color) -> Color_F32 { return { f32(color.r) / 255, f32(color.g) / 255, f32(color.b) / 255, f32(color.a) / 255, } } _clear :: proc(color: Color) { c := f32_color_from_color(color) s.device_context->ClearRenderTargetView(s.framebuffer_view, &c) s.device_context->ClearDepthStencilView(s.depth_buffer_view, {.DEPTH}, 1, 0) } _Texture :: struct { handle: Texture_Handle, tex: ^d3d11.ITexture2D, view: ^d3d11.IShaderResourceView, } _load_texture_from_file :: proc(filename: string) -> Texture { img, img_err := image.load_from_file(filename, options = {.alpha_add_if_missing}, allocator = context.temp_allocator) if img_err != nil { log.errorf("Error loading texture %v: %v", filename, img_err) return {} } return _load_texture_from_memory(img.pixels.buf[:], img.width, img.height) } _load_texture_from_memory :: proc(data: []u8, width: int, height: int) -> Texture { texture_desc := d3d11.TEXTURE2D_DESC{ Width = u32(width), Height = u32(height), MipLevels = 1, ArraySize = 1, // TODO: _SRGB or not? Format = .R8G8B8A8_UNORM, SampleDesc = {Count = 1}, Usage = .IMMUTABLE, BindFlags = {.SHADER_RESOURCE}, } texture_data := d3d11.SUBRESOURCE_DATA{ pSysMem = raw_data(data), SysMemPitch = u32(width * 4), } texture: ^d3d11.ITexture2D s.device->CreateTexture2D(&texture_desc, &texture_data, &texture) texture_view: ^d3d11.IShaderResourceView s.device->CreateShaderResourceView(texture, nil, &texture_view) tex := _Texture { tex = texture, view = texture_view, } handle := hm.add(&s.textures, tex) return { id = handle, width = width, height = height, } } _destroy_texture :: proc(tex: Texture) { if t := hm.get(&s.textures, tex.id); t != nil { t.tex->Release() t.view->Release() } hm.remove(&s.textures, tex.id) } _draw_texture :: proc(tex: Texture, pos: Vec2, tint := WHITE) { _draw_texture_ex( tex, {0, 0, f32(tex.width), f32(tex.height)}, {pos.x, pos.y, f32(tex.width), f32(tex.height)}, {}, 0, tint, ) } _draw_texture_rect :: proc(tex: Texture, rect: Rect, pos: Vec2, tint := WHITE) { _draw_texture_ex( tex, rect, {pos.x, pos.y, rect.w, rect.h}, {}, 0, tint, ) } 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 } s.vertex_buffer_cpu_used += shd.vertex_size } _draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rot: f32, tint := WHITE) { if tex.width == 0 || tex.height == 0 { return } if s.batch_texture != TEXTURE_NONE && s.batch_texture != tex.id { _draw_current_batch() } r := dst r.x -= origin.x r.y -= origin.y s.batch_texture = tex.id tl, tr, bl, br: Vec2 // Rotation adapted from Raylib's "DrawTexturePro" if rot == 0 { x := dst.x - origin.x y := dst.y - origin.y tl = { x, y } tr = { x + dst.w, y } bl = { x, y + dst.h } br = { x + dst.w, y + dst.h } } else { sin_rot := math.sin(rot * math.RAD_PER_DEG) cos_rot := math.cos(rot * math.RAD_PER_DEG) x := dst.x y := dst.y dx := -origin.x dy := -origin.y tl = { x + dx * cos_rot - dy * sin_rot, y + dx * sin_rot + dy * cos_rot, } tr = { x + (dx + dst.w) * cos_rot - dy * sin_rot, y + (dx + dst.w) * sin_rot + dy * cos_rot, } bl = { x + dx * cos_rot - (dy + dst.h) * sin_rot, y + dx * sin_rot + (dy + dst.h) * cos_rot, } br = { x + (dx + dst.w) * cos_rot - (dy + dst.h) * sin_rot, y + (dx + dst.w) * sin_rot + (dy + dst.h) * cos_rot, } } ts := Vec2{f32(tex.width), f32(tex.height)} up := Vec2{src.x, src.y} / ts us := Vec2{src.w, src.h} / ts c := tint 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) } _draw_rectangle :: proc(r: Rect, c: Color) { if s.batch_texture != TEXTURE_NONE && s.batch_texture != s.shape_drawing_texture.id { _draw_current_batch() } s.batch_texture = s.shape_drawing_texture.id 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_rectangle_outline :: proc(r: Rect, thickness: f32, color: Color) { t := thickness // Based on DrawRectangleLinesEx from Raylib top := Rect { r.x, r.y, r.w, t, } bottom := Rect { r.x, r.y + r.h - t, r.w, t, } left := Rect { r.x, r.y + t, t, r.h - t * 2, } right := Rect { r.x + r.w - t, r.y + t, t, r.h - t * 2, } _draw_rectangle(top, color) _draw_rectangle(bottom, color) _draw_rectangle(left, color) _draw_rectangle(right, color) } _draw_circle :: proc(center: Vec2, radius: f32, color: Color) { } _draw_line :: proc(start: Vec2, end: Vec2, thickness: f32, color: Color) { } _get_screen_width :: proc() -> int { return s.width } _get_screen_height :: proc() -> int { return s.height } _key_went_down :: proc(key: Keyboard_Key) -> bool { return s.keys_went_down[key] } _key_went_up :: proc(key: Keyboard_Key) -> bool { return s.keys_went_up[key] } _key_is_held :: proc(key: Keyboard_Key) -> bool { return s.keys_is_held[key] } _window_should_close :: proc() -> bool { return !s.run } _draw_text :: proc(text: string, pos: Vec2, font_size: f32, color: Color) { } _mouse_button_pressed :: proc(button: Mouse_Button) -> bool { return false } _mouse_button_released :: proc(button: Mouse_Button) -> bool { return false } _mouse_button_held :: proc(button: Mouse_Button) -> bool { return false } _mouse_wheel_delta :: proc() -> f32 { return 0 } _mouse_position :: proc() -> Vec2 { return {} } _enable_scissor :: proc(x, y, w, h: int) { } _disable_scissor :: proc() { } _set_window_size :: proc(width: int, height: int) { } _set_window_position :: proc(x: int, y: int) { // TODO: Does x, y respect monitor DPI? win32.SetWindowPos( s.window, {}, i32(x), i32(y), 0, 0, win32.SWP_NOACTIVATE | win32.SWP_NOZORDER | win32.SWP_NOSIZE, ) } _screen_to_world :: proc(pos: Vec2, camera: Camera) -> Vec2 { return pos } Vec3 :: [3]f32 vec3_from_vec2 :: proc(v: Vec2) -> Vec3 { return { v.x, v.y, 0, } } _set_camera :: proc(camera: Maybe(Camera)) { if camera == s.batch_camera { return } _draw_current_batch() s.batch_camera = camera if c, c_ok := camera.?; c_ok { origin_trans := linalg.matrix4_translate(vec3_from_vec2(-c.origin)) translate := linalg.matrix4_translate(vec3_from_vec2(c.target)) rot := linalg.matrix4_rotate_f32(c.rotation * math.RAD_PER_DEG, {0, 0, 1}) camera_matrix := translate * rot * origin_trans s.view_matrix = linalg.inverse(camera_matrix) s.proj_matrix = make_default_projection(s.width, s.height) s.proj_matrix[0, 0] *= c.zoom s.proj_matrix[1, 1] *= c.zoom } else { s.proj_matrix = make_default_projection(s.width, s.height) s.view_matrix = 1 } } _set_scissor_rect :: proc(scissor_rect: Maybe(Rect)) { } _set_shader :: proc(shader: Shader_Handle) { if shader == s.batch_shader { return } _draw_current_batch() s.batch_shader = shader } _set_shader_vertex_layout :: proc(shader: Shader_Handle, layout: []Shader_Input_Format) { shd := hm.get(&s.shaders, shader) if shd == nil { log.error("Invalid shader") return } if len(layout) != len(shd.inputs) { log.error("Shader has %v inputs but layout only specifies %v. The inputs are:", len(shd.inputs), len(layout)) for i in shd.inputs { log.error(i) } return } for _, idx in shd.inputs { shd.inputs[idx].format = layout[idx] } } _set_shader_constant :: proc(shader: Shader_Handle, loc: Shader_Constant_Location, val: $T) { _draw_current_batch() shd := hm.get(&s.shaders, shader) if shd == nil { return } 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_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) } _process_events :: proc() { s.keys_went_up = {} s.keys_went_down = {} msg: win32.MSG for win32.PeekMessageW(&msg, nil, 0, 0, win32.PM_REMOVE) { win32.TranslateMessage(&msg) win32.DispatchMessageW(&msg) } } _draw_current_batch :: proc() { if s.vertex_buffer_cpu_used == s.vertex_buffer_offset { return } viewport := d3d11.VIEWPORT{ 0, 0, f32(s.width), f32(s.height), 0, 1, } dc := s.device_context vb_data: d3d11.MAPPED_SUBRESOURCE ch(dc->Map(s.vertex_buffer_gpu, 0, .WRITE_NO_OVERWRITE, {}, &vb_data)) { 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], ) } 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) 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) for mloc, builtin in shd.constant_builtin_locations { loc, loc_ok := mloc.? if !loc_ok { continue } switch builtin { case .MVP: dst := (^matrix[4,4]f32)(&shd.constant_buffers[loc.buffer_idx].cpu_data[loc.offset]) dst^ = s.proj_matrix * s.view_matrix } } dc->VSSetShader(shd.vertex_shader, nil, 0) for &c, c_idx in shd.constant_buffers { if c.gpu_data == nil { continue } cb_data: d3d11.MAPPED_SUBRESOURCE ch(dc->Map(c.gpu_data, 0, .WRITE_DISCARD, {}, &cb_data)) mem.copy(cb_data.pData, raw_data(c.cpu_data), len(c.cpu_data)) dc->Unmap(c.gpu_data, 0) dc->VSSetConstantBuffers(u32(c_idx), 1, &c.gpu_data) dc->PSSetConstantBuffers(u32(c_idx), 1, &c.gpu_data) } dc->RSSetViewports(1, &viewport) dc->RSSetState(s.rasterizer_state) dc->PSSetShader(shd.pixel_shader, nil, 0) if t := hm.get(&s.textures, s.batch_texture); t != nil { dc->PSSetShaderResources(0, 1, &t.view) } dc->PSSetSamplers(0, 1, &s.sampler_state) dc->OMSetRenderTargets(1, &s.framebuffer_view, s.depth_buffer_view) 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 log_messages() } 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) } _present :: proc() { _draw_current_batch() ch(s.swapchain->Present(1, {})) s.vertex_buffer_offset = 0 s.vertex_buffer_cpu_used = 0 } Shader_Constant_Location :: struct { buffer_idx: u32, offset: u32, } _load_shader :: proc(shader: string) -> Shader_Handle { 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)) if vs_blob_errors != nil { log.error("Failed compiling shader:") log.error(strings.string_from_ptr((^u8)(vs_blob_errors->GetBufferPointer()), int(vs_blob_errors->GetBufferSize()))) } vertex_shader: ^d3d11.IVertexShader ch(s.device->CreateVertexShader(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), nil, &vertex_shader)) ref: ^d3d11.IShaderReflection ch(d3d_compiler.Reflect(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), d3d11.ID3D11ShaderReflection_UUID, (^rawptr)(&ref))) 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 { context.allocator = s.allocator d: d3d11.SHADER_DESC ch(ref->GetDesc(&d)) inputs = make([]Shader_Input, d.InputParameters) for in_idx in 0..GetInputParameterDesc(in_idx, &in_desc)) < 0 { log.errorf("Invalid input: %v in shader %v", in_idx, shader) continue } type: Shader_Input_Type if in_desc.SemanticIndex > 0 { log.errorf("Matrix shader input types not yet implemented") continue } switch in_desc.ComponentType { case .UNKNOWN: log.errorf("Unknown component type") case .UINT32: log.errorf("Not implemented") case .SINT32: log.errorf("Not implemented") case .FLOAT32: switch in_desc.Mask { case 0: log.errorf("Invalid input mask"); continue case 1: type = .F32 case 3: type = .Vec2 case 7: type = .Vec3 case 15: type = .Vec4 } } inputs[in_idx] = { name = strings.clone_from_cstring(in_desc.SemanticName), register = int(in_idx), type = type, } } constant_buffers = make([]Shader_Constant_Buffer, d.ConstantBuffers) for cb_idx in 0..GetConstantBufferByIndex(cb_idx) if cb_info == nil { continue } cb_desc: d3d11.SHADER_BUFFER_DESC cb_info->GetDesc(&cb_desc) if cb_desc.Size == 0 { continue } b := &constant_buffers[cb_idx] b.cpu_data = make([]u8, cb_desc.Size, s.allocator) constant_buffer_desc := d3d11.BUFFER_DESC{ ByteWidth = cb_desc.Size, Usage = .DYNAMIC, BindFlags = {.CONSTANT_BUFFER}, CPUAccessFlags = {.WRITE}, } ch(s.device->CreateBuffer(&constant_buffer_desc, nil, &b.gpu_data)) for var_idx in 0..GetVariableByIndex(var_idx) if var_info == nil { continue } var_desc: d3d11.SHADER_VARIABLE_DESC var_info->GetDesc(&var_desc) if var_desc.Name != "" { loc := Shader_Constant_Location { buffer_idx = cb_idx, offset = var_desc.StartOffset, } constant_lookup[strings.clone_from_cstring(var_desc.Name)] = loc switch var_desc.Name { case "mvp": constant_builtin_locations[.MVP] = loc } } // TODO add the size or type somewhere so we set it correctly /*log.info(var_desc) type_info := var_info->GetType() type_info_desc: d3d11.SHADER_TYPE_DESC type_info->GetDesc(&type_info_desc) log.info(type_info_desc)*/ } } } default_input_offsets: [Shader_Default_Inputs]int for &d in default_input_offsets { d = -1 } input_offset: int for &i in inputs { if i.name == "POS" && i.type == .Vec2 { i.format = .RG32_Float default_input_offsets[.Position] = input_offset } else if i.name == "UV" && i.type == .Vec2 { i.format = .RG32_Float default_input_offsets[.UV] = input_offset } else if i.name == "COL" && i.type == .Vec4 { i.format = .RGBA8_Norm default_input_offsets[.Color] = input_offset } else { switch i.type { case .F32: i.format = .R32_Float case .Vec2: i.format = .RG32_Float case .Vec3: i.format = .RGBA32_Float case .Vec4: i.format = .RGBA32_Float } } input_offset += shader_input_format_size(i.format) } input_layout_desc := make([]d3d11.INPUT_ELEMENT_DESC, len(inputs), context.temp_allocator) for idx in 0..CreateInputLayout(raw_data(input_layout_desc), u32(len(input_layout_desc)), vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), &input_layout)) ps_blob: ^d3d11.IBlob ps_blob_errors: ^d3d11.IBlob ch(d3d_compiler.Compile(raw_data(shader), len(shader), nil, nil, nil, "ps_main", "ps_5_0", 0, 0, &ps_blob, &ps_blob_errors)) if ps_blob_errors != nil { log.error("Failed compiling shader:") log.error(strings.string_from_ptr((^u8)(ps_blob_errors->GetBufferPointer()), int(ps_blob_errors->GetBufferSize()))) } pixel_shader: ^d3d11.IPixelShader ch(s.device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), nil, &pixel_shader)) shd := Shader { vertex_shader = vertex_shader, pixel_shader = pixel_shader, input_layout = input_layout, constant_buffers = constant_buffers, constant_lookup = constant_lookup, constant_builtin_locations = constant_builtin_locations, inputs = inputs, 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 { switch f { case .RGBA32_Float: return .R32G32B32A32_FLOAT case .RGBA8_Norm: return .R8G8B8A8_UNORM case .RGBA8_Norm_SRGB: return .R8G8B8A8_UNORM_SRGB case .RG32_Float: return .R32G32_FLOAT case .R32_Float: return .R32_FLOAT } log.error("Unknown format") return .UNKNOWN } shader_input_format_size :: proc(f: Shader_Input_Format) -> int { switch f { case .RGBA32_Float: return 32 case .RGBA8_Norm: return 4 case .RGBA8_Norm_SRGB: return 4 case .RG32_Float: return 8 case .R32_Float: return 4 } 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) for k,_ in shd.constant_lookup { delete(k) } delete(shd.constant_lookup) for i in shd.inputs { delete(i.name) } delete(shd.inputs) } hm.remove(&s.shaders, shader) } _get_shader_constant_location :: proc(shader: Shader_Handle, name: string) -> Shader_Constant_Location { shd := hm.get(&s.shaders, shader) if shd == nil { return {} } return shd.constant_lookup[name] } temp_cstring :: proc(str: string, loc := #caller_location) -> cstring { return strings.clone_to_cstring(str, context.temp_allocator, loc) } // CHeck win errors and print message log if there is any error ch :: proc(hr: win32.HRESULT, loc := #caller_location) -> win32.HRESULT { if hr >= 0 { return hr } log.errorf("d3d11 error: %0x", u32(hr), location = loc) log_messages(loc) return hr } log_messages :: proc(loc := #caller_location) { iq := s.info_queue if iq == nil { return } n := iq->GetNumStoredMessages() longest_msg: d3d11.SIZE_T for i in 0..=n { msglen: d3d11.SIZE_T iq->GetMessage(i, nil, &msglen) if msglen > longest_msg { longest_msg = msglen } } if longest_msg > 0 { msg_raw_ptr, _ := (mem.alloc(int(longest_msg), allocator = context.temp_allocator)) for i in 0..=n { msglen: d3d11.SIZE_T iq->GetMessage(i, nil, &msglen) if msglen > 0 { msg := (^d3d11.MESSAGE)(msg_raw_ptr) iq->GetMessage(i, msg, &msglen) log.error(msg.pDescription, location = loc) } } } iq->ClearStoredMessages() }