├── examples ├── snake │ ├── .gitignore │ ├── body.png │ ├── food.png │ ├── head.png │ ├── tail.png │ ├── shader.hlsl │ ├── LICENSE │ └── snake.odin ├── minimal │ ├── sixten.jpg │ └── minimal.odin ├── premultiplied_alpha │ ├── plop.png │ └── premultiplied_alpha.odin ├── fonts │ ├── cat_and_onion_dialogue_font.ttf │ └── fonts.odin ├── raylib_ports │ ├── bunnymark │ │ ├── wabbit_alpha.png │ │ └── bunnymark.odin │ ├── shaders_texture_waves │ │ ├── space.png │ │ ├── wave.hlsl │ │ └── shaders_texture_waves.odin │ ├── LICENSE │ └── 2d_camera │ │ └── 2d_camera.odin ├── multitexture │ ├── gl_multitexture_fragment_shader.glsl │ ├── gl_multitexture_vertex_shader.glsl │ ├── multitexture_shader.hlsl │ └── multitexture.odin ├── render_texture │ └── render_texture.odin ├── gamepad │ └── gamepad.odin └── box2d │ └── karl2d_box2d.odin ├── roboto.ttf ├── .gitignore ├── render_backend_gl_default_fragment_shader.glsl ├── window_interface_chooser.odin ├── render_backend_gl_default_vertex_shader.glsl ├── render_backend_d3d11_default_shader.hlsl ├── README.md ├── render_backend_chooser.odin ├── LICENSE ├── .github └── workflows │ └── build.yml ├── api_doc_builder └── api_doc_builder.odin ├── window_interface.odin ├── render_backend_interface.odin ├── window_js.odin ├── render_backend_gl_windows.odin ├── TODO.md ├── .sublime └── karl2d.sublime-project ├── handle_map └── handle_map.odin ├── window_win32.odin ├── karl2d.doc.odin ├── render_backend_gl.odin ├── render_backend_webgl.odin └── render_backend_d3d11.odin /examples/snake/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe -------------------------------------------------------------------------------- /roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/roboto.ttf -------------------------------------------------------------------------------- /examples/snake/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/snake/body.png -------------------------------------------------------------------------------- /examples/snake/food.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/snake/food.png -------------------------------------------------------------------------------- /examples/snake/head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/snake/head.png -------------------------------------------------------------------------------- /examples/snake/tail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/snake/tail.png -------------------------------------------------------------------------------- /examples/minimal/sixten.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/minimal/sixten.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | raylib/ 4 | *.pdb 5 | *.exe 6 | *.rdi 7 | !/.sublime/karl2d.sublime-project -------------------------------------------------------------------------------- /examples/premultiplied_alpha/plop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/premultiplied_alpha/plop.png -------------------------------------------------------------------------------- /examples/fonts/cat_and_onion_dialogue_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/fonts/cat_and_onion_dialogue_font.ttf -------------------------------------------------------------------------------- /examples/raylib_ports/bunnymark/wabbit_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/raylib_ports/bunnymark/wabbit_alpha.png -------------------------------------------------------------------------------- /examples/raylib_ports/shaders_texture_waves/space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karl-zylinski/karl2d/HEAD/examples/raylib_ports/shaders_texture_waves/space.png -------------------------------------------------------------------------------- /render_backend_gl_default_fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec2 frag_texcoord; 3 | in vec4 frag_color; 4 | out vec4 final_color; 5 | 6 | uniform sampler2D tex; 7 | 8 | void main() 9 | { 10 | vec4 c = texture(tex, frag_texcoord); 11 | final_color = c * frag_color; 12 | } -------------------------------------------------------------------------------- /window_interface_chooser.odin: -------------------------------------------------------------------------------- 1 | package karl2d 2 | 3 | when ODIN_OS == .Windows { 4 | DEFAULT_WINDOW_INTERFACE :: WINDOW_INTERFACE_WIN32 5 | } else when ODIN_OS == .JS { 6 | DEFAULT_WINDOW_INTERFACE :: WINDOW_INTERFACE_JS 7 | } 8 | 9 | WINDOW_INTERFACE :: DEFAULT_WINDOW_INTERFACE 10 | -------------------------------------------------------------------------------- /examples/multitexture/gl_multitexture_fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec2 frag_texcoord; 3 | in vec4 frag_color; 4 | out vec4 final_color; 5 | 6 | uniform sampler2D tex; 7 | uniform sampler2D tex2; 8 | 9 | void main() 10 | { 11 | vec4 c = texture(tex, frag_texcoord); 12 | vec4 c2 = texture(tex2, frag_texcoord); 13 | final_color = c * frag_color * c2; 14 | } -------------------------------------------------------------------------------- /render_backend_gl_default_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | layout(location = 0) in vec3 position; 3 | layout(location = 1) in vec2 texcoord; 4 | layout(location = 2) in vec4 color; 5 | 6 | out vec2 frag_texcoord; 7 | out vec4 frag_color; 8 | 9 | uniform mat4 mvp; 10 | 11 | void main() 12 | { 13 | frag_texcoord = texcoord; 14 | frag_color = color; 15 | gl_Position = mvp * vec4(position, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /examples/multitexture/gl_multitexture_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | layout(location = 0) in vec3 position; 3 | layout(location = 1) in vec2 texcoord; 4 | layout(location = 2) in vec4 color; 5 | 6 | out vec2 frag_texcoord; 7 | out vec4 frag_color; 8 | 9 | uniform mat4 mvp; 10 | 11 | void main() 12 | { 13 | frag_texcoord = texcoord; 14 | frag_color = color; 15 | gl_Position = mvp * vec4(position, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /render_backend_d3d11_default_shader.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer constants : register(b0) { 2 | float4x4 mvp; 3 | } 4 | struct vs_in { 5 | float3 position : position; 6 | float2 texcoord : texcoord; 7 | float4 color : color; 8 | }; 9 | struct vs_out { 10 | float4 position : SV_POSITION; 11 | float2 texcoord : texcoord; 12 | float4 color : color; 13 | }; 14 | Texture2D tex : register(t0); 15 | SamplerState smp : register(s0); 16 | vs_out vs_main(vs_in input) { 17 | vs_out output; 18 | output.position = mul(mvp, float4(input.position, 1.0f)); 19 | output.texcoord = input.texcoord; 20 | output.color = input.color; 21 | return output; 22 | } 23 | float4 ps_main(vs_out input) : SV_TARGET { 24 | float4 c = tex.Sample(smp, input.texcoord); 25 | return c * input.color; 26 | } -------------------------------------------------------------------------------- /examples/snake/shader.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer constants : register(b0) { 2 | float4x4 mvp; 3 | } 4 | struct vs_in { 5 | float3 position : position; 6 | float2 uv : texcoord; 7 | float4 color : color; 8 | float2 wobble : wobble; 9 | }; 10 | struct vs_out { 11 | float4 position : SV_POSITION; 12 | float2 uv : texcoord; 13 | float4 color : color; 14 | }; 15 | Texture2D tex : register(t0); 16 | SamplerState smp : register(s0); 17 | vs_out vs_main(vs_in input) { 18 | vs_out output; 19 | output.position = mul(mvp, float4(input.position + float3(input.wobble, 0), 1.0f)); 20 | output.uv = input.uv; 21 | output.color = input.color; 22 | return output; 23 | } 24 | float4 ps_main(vs_out input) : SV_TARGET { 25 | float4 c = tex.Sample(smp, input.uv); 26 | return c * input.color; 27 | } -------------------------------------------------------------------------------- /examples/fonts/fonts.odin: -------------------------------------------------------------------------------- 1 | package karl2d_minimal_example 2 | 3 | import k2 "../.." 4 | import "core:log" 5 | 6 | main :: proc() { 7 | context.logger = log.create_console_logger() 8 | k2.init(1080, 1080, "Karl2D Fonts Program") 9 | k2.set_window_position(300, 100) 10 | 11 | cao_font := k2.load_font_from_file("cat_and_onion_dialogue_font.ttf") 12 | default_font := k2.get_default_font() 13 | 14 | for !k2.shutdown_wanted() { 15 | k2.process_events() 16 | k2.clear(k2.BLUE) 17 | 18 | font := default_font 19 | 20 | if k2.key_is_held(.K) { 21 | font = cao_font 22 | } 23 | 24 | k2.draw_text_ex(font, "Hellöpe! Hold K to swap font", {20, 20}, 64, k2.WHITE) 25 | k2.present() 26 | free_all(context.temp_allocator) 27 | } 28 | 29 | k2.destroy_font(cao_font) 30 | k2.shutdown() 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ___This library is NOT ready for use!___ 2 | 3 | karl2d_logo 4 | 5 | Karl2D is a work-in-progress library for creating 2D games using the Odin Programming Language. 6 | 7 | The API is based on Raylib because I like that API. But the implementation is meant to have as few dependencies as possible (only `core` libs and rendering APIs in `vendor`). The API will not be identical to Raylib. I'll modify to fit Odin better etc. 8 | 9 | ## What features of Raylib will not be included? 10 | 11 | * 3D 12 | * Some maths things that can be found in Odin's core libs 13 | 14 | Might not be included: 15 | * Time management (use `core:time` instead) 16 | 17 | --- 18 | 19 | Logo by chris_php 20 | -------------------------------------------------------------------------------- /examples/multitexture/multitexture_shader.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer constants : register(b0) { 2 | float4x4 mvp; 3 | } 4 | struct vs_in { 5 | float3 position : position; 6 | float2 texcoord : texcoord; 7 | float4 color : color; 8 | }; 9 | struct vs_out { 10 | float4 position : SV_POSITION; 11 | float2 texcoord : texcoord; 12 | float4 color : color; 13 | }; 14 | Texture2D tex : register(t0); 15 | SamplerState smp : register(s0); 16 | Texture2D tex2 : register(t1); 17 | SamplerState smp2 : register(s1); 18 | vs_out vs_main(vs_in input) { 19 | vs_out output; 20 | output.position = mul(mvp, float4(input.position, 1.0f)); 21 | output.texcoord = input.texcoord; 22 | output.color = input.color; 23 | return output; 24 | } 25 | float4 ps_main(vs_out input) : SV_TARGET { 26 | float4 c = tex.Sample(smp, input.texcoord); 27 | float4 c2 = tex2.Sample(smp2, input.texcoord); 28 | return c * input.color * c2; 29 | } -------------------------------------------------------------------------------- /render_backend_chooser.odin: -------------------------------------------------------------------------------- 1 | package karl2d 2 | 3 | when ODIN_OS == .Windows { 4 | DEFAULT_RENDER_BACKEND :: RENDER_BACKEND_INTERFACE_D3D11 5 | } else when ODIN_OS == .Linux || ODIN_OS == .Darwin { 6 | DEFAULT_RENDER_BACKEND :: RENDER_BACKEND_INTERFACE_GL 7 | } else when ODIN_OS == .JS { 8 | DEFAULT_RENDER_BACKEND :: RENDER_BACKEND_INTERFACE_WEBGL 9 | } 10 | 11 | CUSTOM_RENDER_BACKEND_STR :: #config(KARL2D_RENDER_BACKEND, "") 12 | 13 | when CUSTOM_RENDER_BACKEND_STR != "" { 14 | when CUSTOM_RENDER_BACKEND_STR == "gl" { 15 | RENDER_BACKEND :: RENDER_BACKEND_INTERFACE_GL 16 | } else when CUSTOM_RENDER_BACKEND_STR == "d3d11" { 17 | RENDER_BACKEND :: RENDER_BACKEND_INTERFACE_D3D11 18 | } else { 19 | #panic(CUSTOM_RENDER_BACKEND_STR + " is not a valid value for KARL2D_RENDER_BACKEND. Available backends are: gl, d3d11") 20 | RENDER_BACKEND :: DEFAULT_RENDER_BACKEND 21 | } 22 | } else { 23 | RENDER_BACKEND :: DEFAULT_RENDER_BACKEND 24 | } -------------------------------------------------------------------------------- /examples/premultiplied_alpha/premultiplied_alpha.odin: -------------------------------------------------------------------------------- 1 | package karl2d_example_premultiplied_alpha 2 | 3 | import k2 "../.." 4 | import "core:log" 5 | 6 | main :: proc() { 7 | context.logger = log.create_console_logger() 8 | 9 | k2.init(1080, 1080, "Karl2D Premultiplied Alpha") 10 | k2.set_window_position(300, 100) 11 | 12 | // Load a texture and premultiply the alpha while loading it. 13 | tex := k2.load_texture_from_file("plop.png", options = { .Premultiply_Alpha }) 14 | 15 | // Set the rendering to use premultiplied alpha when blending. 16 | k2.set_blend_mode(.Premultiplied_Alpha) 17 | 18 | for !k2.shutdown_wanted() { 19 | k2.process_events() 20 | k2.clear(k2.BLUE) 21 | 22 | src := k2.get_texture_rect(tex) 23 | dst := k2.Rect { 20, 100, src.w*20, src.h*20} 24 | k2.draw_texture_ex(tex, src, dst, {}, 0) 25 | 26 | k2.present() 27 | free_all(context.temp_allocator) 28 | } 29 | 30 | k2.destroy_texture(tex) 31 | k2.shutdown() 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Karl Zylinski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/snake/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Karl Zylinski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/raylib_ports/LICENSE: -------------------------------------------------------------------------------- 1 | The examples in the subfolders of this "raylib_ports" folder are adaptation of Raylib examples. The license of the original examples is as follows: 2 | 3 | --- 4 | 5 | Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) 6 | 7 | This software is provided "as-is", without any express or implied warranty. In no event 8 | will the authors be held liable for any damages arising from the use of this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, including commercial 11 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not claim that you 14 | wrote the original software. If you use this software in a product, an acknowledgment 15 | in the product documentation would be appreciated but is not required. 16 | 17 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented 18 | as being the original software. 19 | 20 | 3. This notice may not be removed or altered from any source distribution. 21 | -------------------------------------------------------------------------------- /examples/raylib_ports/shaders_texture_waves/wave.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer constants : register(b0) { 2 | float4x4 mvp; 3 | 4 | float seconds; 5 | float2 size; 6 | float freqX; 7 | float freqY; 8 | float ampX; 9 | float ampY; 10 | float speedX; 11 | float speedY; 12 | } 13 | struct vs_in { 14 | float3 position : position; 15 | float2 texcoord : texcoord; 16 | float4 color : color; 17 | }; 18 | struct vs_out { 19 | float4 position : SV_POSITION; 20 | float2 texcoord : texcoord; 21 | float4 color : color; 22 | }; 23 | Texture2D tex : register(t0); 24 | SamplerState smp : register(s0); 25 | vs_out vs_main(vs_in input) { 26 | vs_out output; 27 | output.position = mul(mvp, float4(input.position, 1.0f)); 28 | output.texcoord = input.texcoord; 29 | output.color = input.color; 30 | return output; 31 | } 32 | float4 ps_main(vs_out input) : SV_TARGET { 33 | float pixelWidth = 1.0/size.x; 34 | float pixelHeight = 1.0/size.y; 35 | float aspect = pixelHeight/pixelWidth; 36 | float boxLeft = 0.0; 37 | float boxTop = 0.0; 38 | 39 | float2 p = input.texcoord; 40 | p.x += cos((input.texcoord.y - boxTop)*freqX/(pixelWidth*750.0) + (seconds*speedX))*ampX*pixelWidth; 41 | p.y += sin((input.texcoord.x - boxLeft)*freqY*aspect/(pixelHeight*750.0) + (seconds*speedY))*ampY*pixelHeight; 42 | 43 | float4 c = tex.Sample(smp, p); 44 | return c * input.color; 45 | } -------------------------------------------------------------------------------- /examples/minimal/minimal.odin: -------------------------------------------------------------------------------- 1 | package karl2d_minimal_example 2 | 3 | import k2 "../.." 4 | import "core:mem" 5 | import "core:log" 6 | import "core:fmt" 7 | 8 | _ :: fmt 9 | _ :: mem 10 | 11 | main :: proc() { 12 | context.logger = log.create_console_logger() 13 | 14 | when ODIN_DEBUG { 15 | track: mem.Tracking_Allocator 16 | mem.tracking_allocator_init(&track, context.allocator) 17 | context.allocator = mem.tracking_allocator(&track) 18 | 19 | defer { 20 | if len(track.allocation_map) > 0 { 21 | for _, entry in track.allocation_map { 22 | fmt.eprintf("%v leaked: %v bytes\n", entry.location, entry.size) 23 | } 24 | } 25 | mem.tracking_allocator_destroy(&track) 26 | } 27 | } 28 | 29 | k2.init(1080, 1080, "Karl2D Minimal Program") 30 | k2.set_window_position(300, 100) 31 | tex := k2.load_texture_from_file("sixten.jpg") 32 | 33 | for !k2.shutdown_wanted() { 34 | k2.process_events() 35 | k2.clear(k2.BLUE) 36 | 37 | k2.draw_rect({10, 10, 60, 60}, k2.GREEN) 38 | k2.draw_rect({20, 20, 40, 40}, k2.BLACK) 39 | k2.draw_circle({120, 40}, 30, k2.BLACK) 40 | k2.draw_circle({120, 40}, 20, k2.GREEN) 41 | k2.draw_text("Hellöpe!", {10, 100}, 64, k2.WHITE) 42 | k2.draw_texture_ex(tex, {0, 0, f32(tex.width), f32(tex.height)}, {10, 200, 900, 500}, {}, 0) 43 | 44 | k2.present() 45 | free_all(context.temp_allocator) 46 | } 47 | 48 | k2.destroy_texture(tex) 49 | k2.shutdown() 50 | } 51 | -------------------------------------------------------------------------------- /examples/render_texture/render_texture.odin: -------------------------------------------------------------------------------- 1 | package karl2d_minimal_example 2 | 3 | import k2 "../.." 4 | import "core:mem" 5 | import "core:log" 6 | import "core:fmt" 7 | 8 | _ :: fmt 9 | _ :: mem 10 | 11 | main :: proc() { 12 | context.logger = log.create_console_logger() 13 | 14 | when ODIN_DEBUG { 15 | track: mem.Tracking_Allocator 16 | mem.tracking_allocator_init(&track, context.allocator) 17 | context.allocator = mem.tracking_allocator(&track) 18 | 19 | defer { 20 | if len(track.allocation_map) > 0 { 21 | for _, entry in track.allocation_map { 22 | fmt.eprintf("%v leaked: %v bytes\n", entry.location, entry.size) 23 | } 24 | } 25 | mem.tracking_allocator_destroy(&track) 26 | } 27 | } 28 | 29 | k2.init(1080, 1080, "Karl2D Render Texture Example") 30 | k2.set_window_position(300, 100) 31 | 32 | render_texture := k2.create_render_texture(128, 128) 33 | 34 | for !k2.shutdown_wanted() { 35 | k2.process_events() 36 | 37 | k2.set_render_texture(render_texture) 38 | k2.clear(k2.BLUE) 39 | 40 | k2.draw_rect({1, 1, 12, 12}, k2.GREEN) 41 | k2.draw_rect({2, 2, 10, 10}, k2.BLACK) 42 | k2.draw_circle({20, 7}, 6, k2.BLACK) 43 | k2.draw_circle({20, 7}, 5, k2.GREEN) 44 | k2.draw_text("Hellöpe!", {1, 20}, 20, k2.WHITE) 45 | 46 | k2.set_render_texture(nil) 47 | 48 | k2.clear(k2.GRAY) 49 | k2.draw_texture_ex(render_texture.texture, {0, 0, 128, 128}, {0, 0, 1080, 1080}, {}, 0, k2.WHITE) 50 | k2.draw_texture(render_texture.texture, {400, 20}, k2.WHITE) 51 | k2.draw_texture_ex(render_texture.texture, {0, 0, 128, 128}, {512, 512, 512, 512}, {}, 70, k2.WHITE) 52 | 53 | k2.present() 54 | free_all(context.temp_allocator) 55 | } 56 | 57 | k2.destroy_render_texture(render_texture) 58 | 59 | k2.shutdown() 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: 0 20 * * * 9 | 10 | 11 | jobs: 12 | build_windows: 13 | name: Windows 14 | runs-on: windows-latest 15 | steps: 16 | - uses: laytan/setup-odin@v2 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | release: false 20 | 21 | - uses: actions/checkout@v4 22 | 23 | - name: minimal 24 | run: odin build examples/minimal -vet -strict-style 25 | 26 | - name: minimal (debug) 27 | run: odin build examples/minimal -vet -strict-style -debug 28 | 29 | - name: minimal (gl) 30 | run: odin build examples/minimal -vet -strict-style -define:KARL2D_RENDER_BACKEND=gl 31 | 32 | - name: minimal (gl, debug) 33 | run: odin build examples/minimal -vet -strict-style -debug -define:KARL2D_RENDER_BACKEND=gl 34 | 35 | - name: snake 36 | run: odin build examples/snake -vet -strict-style 37 | 38 | - name: snake (debug) 39 | run: odin build examples/snake -vet -strict-style -debug 40 | 41 | - name: 2d_camera 42 | run: odin build examples/raylib_ports/2d_camera -vet -strict-style 43 | 44 | - name: 2d_camera (debug) 45 | run: odin build examples/raylib_ports/2d_camera -vet -strict-style -debug 46 | 47 | - name: shaders_texture_waves 48 | run: odin build examples/raylib_ports/shaders_texture_waves -vet -strict-style 49 | 50 | - name: shaders_texture_waves (debug) 51 | run: odin build examples/raylib_ports/shaders_texture_waves -vet -strict-style -debug 52 | 53 | - name: gamepad 54 | run: odin build examples/gamepad -vet -strict-style 55 | 56 | - name: gamepad (debug) 57 | run: odin build examples/gamepad -vet -strict-style -debug 58 | -------------------------------------------------------------------------------- /examples/multitexture/multitexture.odin: -------------------------------------------------------------------------------- 1 | package karl2d_multitexture_example 2 | 3 | import k2 "../.." 4 | import "core:mem" 5 | import "core:log" 6 | import "core:fmt" 7 | 8 | _ :: fmt 9 | _ :: mem 10 | 11 | main :: proc() { 12 | context.logger = log.create_console_logger() 13 | 14 | when ODIN_DEBUG { 15 | track: mem.Tracking_Allocator 16 | mem.tracking_allocator_init(&track, context.allocator) 17 | context.allocator = mem.tracking_allocator(&track) 18 | 19 | defer { 20 | if len(track.allocation_map) > 0 { 21 | for _, entry in track.allocation_map { 22 | fmt.eprintf("%v leaked: %v bytes\n", entry.location, entry.size) 23 | } 24 | } 25 | mem.tracking_allocator_destroy(&track) 26 | } 27 | } 28 | 29 | k2.init(1080, 1080, "Karl2D Multitexture Example") 30 | k2.set_window_position(300, 100) 31 | when k2.CUSTOM_RENDER_BACKEND_STR == "gl" { 32 | shd := k2.load_shader_from_file("gl_multitexture_vertex_shader.glsl", "gl_multitexture_fragment_shader.glsl") 33 | } else { 34 | shd := k2.load_shader_from_file("multitexture_shader.hlsl", "multitexture_shader.hlsl") 35 | } 36 | 37 | tex1 := k2.load_texture_from_file("../minimal/sixten.jpg") 38 | tex2 := k2.load_texture_from_file("../snake/food.png") 39 | 40 | shd.texture_bindpoints[shd.texture_lookup["tex2"]] = tex2.handle 41 | 42 | for !k2.shutdown_wanted() { 43 | k2.process_events() 44 | k2.set_shader(shd) 45 | k2.clear(k2.BLUE) 46 | 47 | k2.draw_rect({10, 10, 60, 60}, k2.GREEN) 48 | k2.draw_rect({20, 20, 40, 40}, k2.BLACK) 49 | k2.draw_circle({120, 40}, 30, k2.BLACK) 50 | k2.draw_circle({120, 40}, 20, k2.GREEN) 51 | k2.draw_text("Hellöpe!", {10, 100}, 64, k2.WHITE) 52 | k2.draw_texture_ex(tex1, {0, 0, f32(tex1.width), f32(tex1.height)}, {10, 200, 900, 500}, {}, 0) 53 | 54 | k2.present() 55 | free_all(context.temp_allocator) 56 | } 57 | 58 | k2.destroy_texture(tex1) 59 | k2.destroy_texture(tex2) 60 | k2.destroy_shader(shd) 61 | k2.shutdown() 62 | } 63 | -------------------------------------------------------------------------------- /api_doc_builder/api_doc_builder.odin: -------------------------------------------------------------------------------- 1 | package karl2d_api_doc_builder 2 | 3 | import os "core:os" 4 | import vmem "core:mem/virtual" 5 | import "core:log" 6 | import "core:fmt" 7 | import "core:odin/parser" 8 | import "core:odin/ast" 9 | import "core:strings" 10 | 11 | main :: proc() { 12 | arena: vmem.Arena 13 | context.allocator = vmem.arena_allocator(&arena) 14 | context.temp_allocator = context.allocator 15 | context.logger = log.create_console_logger() 16 | 17 | pkg_ast, pkg_ast_ok := parser.parse_package_from_path(".") 18 | log.ensuref(pkg_ast_ok, "Could not generate AST for package") 19 | 20 | o, o_err := os.open("karl2d.doc.odin", os.O_CREATE | os.O_TRUNC, 0o644) 21 | log.assertf(o_err == nil, "Couldn't open karl2d.doc.odin: %v", o_err) 22 | 23 | pln :: fmt.fprintln 24 | 25 | pln(o, `// This file is purely documentational. It is generated from the contents of 'karl2d.odin'.`) 26 | pln(o, "#+build ignore") 27 | pln(o, "package karl2d") 28 | 29 | prev_line: int 30 | 31 | for n, &f in pkg_ast.files { 32 | if !strings.ends_with(n, "karl2d.odin") { 33 | continue 34 | } 35 | 36 | decl_loop: for &d in f.decls { 37 | #partial switch &dd in d.derived { 38 | case ^ast.Value_Decl: 39 | val: string 40 | for v, vi in dd.values { 41 | #partial switch vd in v.derived { 42 | case ^ast.Proc_Lit: 43 | name := f.src[dd.names[vi].pos.offset:dd.names[vi].end.offset] 44 | type := f.src[vd.type.pos.offset:vd.type.end.offset] 45 | val = fmt.tprintf("%v :: %v", name, type) 46 | } 47 | } 48 | 49 | if val == "" { 50 | val = f.src[dd.pos.offset:dd.end.offset] 51 | } 52 | 53 | if val == "API_END :: true" { 54 | break decl_loop 55 | } 56 | 57 | if dd.docs != nil { 58 | pln(o, "") 59 | pln(o, f.src[dd.docs.pos.offset:dd.docs.end.offset]) 60 | } else { 61 | if prev_line != dd.pos.line - 1 { 62 | pln(o, "") 63 | } 64 | } 65 | 66 | pln(o, val) 67 | 68 | prev_line = dd.pos.line 69 | } 70 | } 71 | } 72 | 73 | os.close(o) 74 | } 75 | -------------------------------------------------------------------------------- /window_interface.odin: -------------------------------------------------------------------------------- 1 | package karl2d 2 | 3 | import "base:runtime" 4 | 5 | Window_Interface :: struct #all_or_none { 6 | state_size: proc() -> int, 7 | init: proc(window_state: rawptr, window_width: int, window_height: int, window_title: string, 8 | flags: Window_Flags, allocator: runtime.Allocator), 9 | shutdown: proc(), 10 | window_handle: proc() -> Window_Handle, 11 | process_events: proc(), 12 | get_events: proc() -> []Window_Event, 13 | clear_events: proc(), 14 | set_position: proc(x: int, y: int), 15 | set_size: proc(w, h: int), 16 | get_width: proc() -> int, 17 | get_height: proc() -> int, 18 | get_window_scale: proc() -> f32, 19 | set_flags: proc(flags: Window_Flags), 20 | 21 | is_gamepad_active: proc(gamepad: int) -> bool, 22 | get_gamepad_axis: proc(gamepad: int, axis: Gamepad_Axis) -> f32, 23 | set_gamepad_vibration: proc(gamepad: int, left: f32, right: f32), 24 | 25 | set_internal_state: proc(state: rawptr), 26 | } 27 | 28 | Window_Handle :: distinct uintptr 29 | 30 | Window_Event :: union { 31 | Window_Event_Close_Wanted, 32 | Window_Event_Key_Went_Down, 33 | Window_Event_Key_Went_Up, 34 | Window_Event_Mouse_Move, 35 | Window_Event_Mouse_Wheel, 36 | Window_Event_Resize, 37 | Window_Event_Mouse_Button_Went_Down, 38 | Window_Event_Mouse_Button_Went_Up, 39 | Window_Event_Gamepad_Button_Went_Down, 40 | Window_Event_Gamepad_Button_Went_Up, 41 | } 42 | 43 | Window_Event_Key_Went_Down :: struct { 44 | key: Keyboard_Key, 45 | } 46 | 47 | Window_Event_Key_Went_Up :: struct { 48 | key: Keyboard_Key, 49 | } 50 | 51 | Window_Event_Mouse_Button_Went_Down :: struct { 52 | button: Mouse_Button, 53 | } 54 | 55 | Window_Event_Mouse_Button_Went_Up :: struct { 56 | button: Mouse_Button, 57 | } 58 | 59 | Window_Event_Gamepad_Button_Went_Down :: struct { 60 | gamepad: int, 61 | button: Gamepad_Button, 62 | } 63 | 64 | Window_Event_Gamepad_Button_Went_Up :: struct { 65 | gamepad: int, 66 | button: Gamepad_Button, 67 | } 68 | 69 | Window_Event_Close_Wanted :: struct {} 70 | 71 | Window_Event_Mouse_Move :: struct { 72 | position: Vec2, 73 | } 74 | 75 | Window_Event_Mouse_Wheel :: struct { 76 | delta: f32, 77 | } 78 | 79 | Window_Event_Resize :: struct { 80 | width, height: int, 81 | } -------------------------------------------------------------------------------- /render_backend_interface.odin: -------------------------------------------------------------------------------- 1 | package karl2d 2 | 3 | import "base:runtime" 4 | 5 | Shader_Constant_Desc :: struct { 6 | name: string, 7 | size: int, 8 | } 9 | 10 | Shader_Texture_Bindpoint_Desc :: struct { 11 | name: string, 12 | } 13 | 14 | Shader_Desc :: struct { 15 | constants: []Shader_Constant_Desc, 16 | texture_bindpoints: []Shader_Texture_Bindpoint_Desc, 17 | inputs: []Shader_Input, 18 | } 19 | 20 | Render_Backend_Interface :: struct #all_or_none { 21 | state_size: proc() -> int, 22 | init: proc(state: rawptr, window_handle: Window_Handle, swapchain_width, swapchain_height: int, allocator := context.allocator), 23 | shutdown: proc(), 24 | clear: proc(render_target: Render_Target_Handle, color: Color), 25 | present: proc(), 26 | 27 | draw: proc( 28 | shader: Shader, 29 | render_target: Render_Target_Handle, 30 | bound_textures: []Texture_Handle, 31 | scissor: Maybe(Rect), 32 | blend: Blend_Mode, 33 | vertex_buffer: []u8, 34 | ), 35 | 36 | set_internal_state: proc(state: rawptr), 37 | 38 | create_texture: proc(width: int, height: int, format: Pixel_Format) -> Texture_Handle, 39 | load_texture: proc(data: []u8, width: int, height: int, format: Pixel_Format) -> Texture_Handle, 40 | update_texture: proc(handle: Texture_Handle, data: []u8, rect: Rect) -> bool, 41 | destroy_texture: proc(handle: Texture_Handle), 42 | 43 | create_render_texture: proc(width: int, height: int) -> (Texture_Handle, Render_Target_Handle), 44 | destroy_render_target: proc(render_texture: Render_Target_Handle), 45 | 46 | set_texture_filter: proc( 47 | handle: Texture_Handle, 48 | scale_down_filter: Texture_Filter, 49 | scale_up_filter: Texture_Filter, 50 | mip_filter: Texture_Filter, 51 | ), 52 | 53 | load_shader: proc( 54 | vertex_shader_data: []byte, 55 | pixel_shader_data: []byte, 56 | desc_allocator: runtime.Allocator, 57 | layout_formats: []Pixel_Format = {}, 58 | ) -> ( 59 | handle: Shader_Handle, 60 | desc: Shader_Desc, 61 | ), 62 | 63 | destroy_shader: proc(shader: Shader_Handle), 64 | 65 | resize_swapchain: proc(width, height: int), 66 | get_swapchain_width: proc() -> int, 67 | get_swapchain_height: proc() -> int, 68 | flip_z: proc() -> bool, 69 | 70 | default_shader_vertex_source: proc() -> []byte, 71 | default_shader_fragment_source: proc() -> []byte, 72 | } 73 | -------------------------------------------------------------------------------- /examples/raylib_ports/shaders_texture_waves/shaders_texture_waves.odin: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/raysan5/raylib/blob/master/examples/shaders/shaders_texture_waves.c 2 | 3 | package raylib_example_shaders_texture_waves 4 | 5 | import k2 "../../.." 6 | import "core:time" 7 | import "core:log" 8 | 9 | SCREEN_WIDTH :: 800 10 | SCREEN_HEIGHT :: 450 11 | 12 | main :: proc() { 13 | context.logger = log.create_console_logger() 14 | 15 | k2.init(SCREEN_WIDTH, SCREEN_HEIGHT, "Karl2D: texture waves (raylib [shaders] example - texture waves)") 16 | 17 | texture := k2.load_texture_from_file("space.png") 18 | 19 | WAVE_SHADER_DATA :: #load("wave.hlsl") 20 | 21 | shader := k2.load_shader_from_memory(WAVE_SHADER_DATA, WAVE_SHADER_DATA) 22 | seconds_loc := shader.constant_lookup["seconds"] 23 | freq_x_loc := shader.constant_lookup["freqX"] 24 | freq_y_loc := shader.constant_lookup["freqY"] 25 | amp_x_loc := shader.constant_lookup["ampX"] 26 | amp_y_loc := shader.constant_lookup["ampY"] 27 | speed_x_loc := shader.constant_lookup["speedX"] 28 | speed_y_loc := shader.constant_lookup["speedY"] 29 | 30 | freq_x := f32(25) 31 | freq_y := f32(25) 32 | amp_x := f32(5) 33 | amp_y := f32(5) 34 | speed_x := f32(8) 35 | speed_y := f32(8) 36 | 37 | screen_size := [2]f32 { f32(k2.get_screen_width()), f32(k2.get_screen_height()) } 38 | k2.set_shader_constant(shader, shader.constant_lookup["size"], screen_size) 39 | k2.set_shader_constant(shader, freq_x_loc, freq_x) 40 | k2.set_shader_constant(shader, freq_y_loc, freq_y) 41 | k2.set_shader_constant(shader, amp_x_loc, amp_x) 42 | k2.set_shader_constant(shader, amp_y_loc, amp_y) 43 | k2.set_shader_constant(shader, speed_x_loc, speed_x) 44 | k2.set_shader_constant(shader, speed_y_loc, speed_y) 45 | 46 | seconds: f32 47 | 48 | last_frame_time := time.now() 49 | 50 | for !k2.shutdown_wanted() { 51 | k2.process_events() 52 | now := time.now() 53 | dt := f32(time.duration_seconds(time.diff(last_frame_time, now))) 54 | last_frame_time = now 55 | seconds += dt 56 | 57 | k2.set_shader_constant(shader, seconds_loc, seconds) 58 | k2.set_shader(shader) 59 | 60 | k2.draw_texture(texture, {0, 0}) 61 | k2.draw_texture(texture, {f32(texture.width), 0}) 62 | 63 | k2.set_shader(nil) 64 | k2.present() 65 | } 66 | 67 | k2.destroy_shader(shader) 68 | k2.destroy_texture(texture) 69 | 70 | k2.shutdown() 71 | } -------------------------------------------------------------------------------- /examples/raylib_ports/bunnymark/bunnymark.odin: -------------------------------------------------------------------------------- 1 | // This is a port of https://www.raylib.com/examples/textures/loader.html?name=textures_bunnymark 2 | 3 | package karl2d_bunnymark 4 | 5 | import k2 "../../.." 6 | import "core:math/rand" 7 | import "core:log" 8 | import "core:fmt" 9 | import "core:time" 10 | 11 | MAX_BUNNIES :: 50000 12 | 13 | Bunny :: struct { 14 | position: k2.Vec2, 15 | speed: k2.Vec2, 16 | rot: f32, 17 | rot_speed: f32, 18 | color: k2.Color, 19 | } 20 | 21 | main :: proc() { 22 | context.logger = log.create_console_logger() 23 | 24 | SCREEN_WIDTH :: 800 25 | SCREEN_HEIGHT :: 450 26 | 27 | k2.init(SCREEN_WIDTH, SCREEN_HEIGHT, "bunnymark (raylib port)", window_creation_flags = { .Resizable }) 28 | 29 | tex_bunny := k2.load_texture_from_file("wabbit_alpha.png") 30 | 31 | bunnies: [dynamic]Bunny 32 | prev_time := time.now() 33 | 34 | for !k2.shutdown_wanted() { 35 | cur_time := time.now() 36 | dt := f32(time.duration_seconds(time.diff(prev_time, cur_time))) 37 | prev_time = cur_time 38 | 39 | if k2.mouse_button_is_held(.Left) { 40 | for _ in 0..<100 { 41 | append(&bunnies, Bunny { 42 | position = k2.get_mouse_position(), 43 | speed = { 44 | rand.float32_range(-250, 250)/60, 45 | rand.float32_range(-250, 250)/60, 46 | }, 47 | rot_speed = rand.float32_range(-5, 5), 48 | color = { 49 | u8(rand.int_max(190) + 50), 50 | u8(rand.int_max(160) + 80), 51 | u8(rand.int_max(140) + 100), 52 | 255, 53 | }, 54 | }) 55 | } 56 | } 57 | 58 | for &b in bunnies { 59 | b.position += b.speed 60 | b.rot += b.rot_speed 61 | 62 | if b.position.x > f32(k2.get_screen_width()) || b.position.x < 0 { 63 | b.speed.x *= -1 64 | b.rot_speed = rand.float32_range(-5, 5) 65 | } 66 | 67 | if b.position.y > f32(k2.get_screen_height()) || b.position.y < 0 { 68 | b.speed.y *= -1 69 | b.rot_speed = rand.float32_range(-5, 5) 70 | } 71 | } 72 | 73 | k2.process_events() 74 | k2.clear(k2.RL_WHITE) 75 | 76 | src := k2.Rect { 77 | 0, 0, 78 | f32(tex_bunny.width), f32(tex_bunny.height), 79 | } 80 | 81 | for &b in bunnies { 82 | dest := src 83 | dest.x = b.position.x 84 | dest.y = b.position.y 85 | k2.draw_texture_ex(tex_bunny, src, dest, {dest.w/2, dest.h/2}, b.rot, b.color) 86 | } 87 | 88 | if k2.key_went_down(.B) { 89 | fmt.println(len(bunnies)) 90 | fmt.println(1/dt) 91 | } 92 | 93 | k2.present() 94 | } 95 | 96 | delete(bunnies) 97 | k2.destroy_texture(tex_bunny) 98 | 99 | k2.shutdown() 100 | } -------------------------------------------------------------------------------- /window_js.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | #+private file 3 | 4 | package karl2d 5 | 6 | @(private="package") 7 | WINDOW_INTERFACE_JS :: Window_Interface { 8 | state_size = js_state_size, 9 | init = js_init, 10 | shutdown = js_shutdown, 11 | window_handle = js_window_handle, 12 | process_events = js_process_events, 13 | get_events = js_get_events, 14 | get_width = js_get_width, 15 | get_height = js_get_height, 16 | clear_events = js_clear_events, 17 | set_position = js_set_position, 18 | set_size = js_set_size, 19 | get_window_scale = js_get_window_scale, 20 | set_flags = js_set_flags, 21 | is_gamepad_active = js_is_gamepad_active, 22 | get_gamepad_axis = js_get_gamepad_axis, 23 | set_gamepad_vibration = js_set_gamepad_vibration, 24 | 25 | set_internal_state = js_set_internal_state, 26 | } 27 | 28 | import "core:sys/wasm/js" 29 | import "base:runtime" 30 | import "core:log" 31 | 32 | js_state_size :: proc() -> int { 33 | return size_of(Js_State) 34 | } 35 | 36 | js_init :: proc(window_state: rawptr, window_width: int, window_height: int, window_title: string, 37 | flags: Window_Flags, allocator: runtime.Allocator) { 38 | } 39 | 40 | js_shutdown :: proc() { 41 | } 42 | 43 | js_window_handle :: proc() -> Window_Handle { 44 | return {} 45 | } 46 | 47 | js_process_events :: proc() { 48 | 49 | } 50 | 51 | js_get_events :: proc() -> []Window_Event { 52 | return {} 53 | } 54 | 55 | js_get_width :: proc() -> int { 56 | return 0 57 | } 58 | 59 | js_get_height :: proc() -> int { 60 | return 0 61 | } 62 | 63 | js_clear_events :: proc() { 64 | } 65 | 66 | js_set_position :: proc(x: int, y: int) { 67 | log.error("set_position not implemented in JS") 68 | } 69 | 70 | js_set_size :: proc(w, h: int) { 71 | } 72 | 73 | js_get_window_scale :: proc() -> f32 { 74 | return f32(js.device_pixel_ratio()) 75 | } 76 | 77 | js_set_flags :: proc(flags: Window_Flags) { 78 | } 79 | 80 | js_is_gamepad_active :: proc(gamepad: int) -> bool { 81 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 82 | return false 83 | } 84 | 85 | return false 86 | } 87 | 88 | js_get_gamepad_axis :: proc(gamepad: int, axis: Gamepad_Axis) -> f32 { 89 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 90 | return 0 91 | } 92 | 93 | return 0 94 | } 95 | 96 | js_set_gamepad_vibration :: proc(gamepad: int, left: f32, right: f32) { 97 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 98 | return 99 | } 100 | } 101 | 102 | js_set_internal_state :: proc(state: rawptr) { 103 | assert(state != nil) 104 | s = (^Js_State)(state) 105 | } 106 | 107 | Js_State :: struct { 108 | allocator: runtime.Allocator, 109 | } 110 | 111 | s: ^Js_State 112 | 113 | @(private="package") 114 | HTML_Canvas_ID :: string 115 | -------------------------------------------------------------------------------- /render_backend_gl_windows.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | 3 | package karl2d 4 | 5 | import win32 "core:sys/windows" 6 | import gl "vendor:OpenGL" 7 | import "core:log" 8 | 9 | GL_Context :: win32.HGLRC 10 | 11 | _gl_get_context :: proc(window_handle: Window_Handle) -> (GL_Context, bool) { 12 | hdc := win32.GetWindowDC(win32.HWND(window_handle)) 13 | 14 | pfd := win32.PIXELFORMATDESCRIPTOR { 15 | size_of(win32.PIXELFORMATDESCRIPTOR), 16 | 1, 17 | win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL | win32.PFD_DOUBLEBUFFER, // Flags 18 | win32.PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette. 19 | 32, // Colordepth of the framebuffer. 20 | 0, 0, 0, 0, 0, 0, 21 | 0, 22 | 0, 23 | 0, 24 | 0, 0, 0, 0, 25 | 24, // Number of bits for the depthbuffer 26 | 8, // Number of bits for the stencilbuffer 27 | 0, // Number of Aux buffers in the framebuffer. 28 | win32.PFD_MAIN_PLANE, 29 | 0, 30 | 0, 0, 0, 31 | } 32 | 33 | fmt := win32.ChoosePixelFormat(hdc, &pfd) 34 | win32.SetPixelFormat(hdc, fmt, &pfd) 35 | dummy_ctx := win32.wglCreateContext(hdc) 36 | 37 | win32.wglMakeCurrent(hdc, dummy_ctx) 38 | 39 | win32.gl_set_proc_address(&win32.wglChoosePixelFormatARB, "wglChoosePixelFormatARB") 40 | win32.gl_set_proc_address(&win32.wglCreateContextAttribsARB, "wglCreateContextAttribsARB") 41 | win32.gl_set_proc_address(&win32.wglSwapIntervalEXT, "wglSwapIntervalEXT") 42 | 43 | if win32.wglChoosePixelFormatARB == nil { 44 | log.error("Failed fetching wglChoosePixelFormatARB") 45 | return {}, false 46 | } 47 | 48 | if win32.wglCreateContextAttribsARB == nil { 49 | log.error("Failed fetching wglCreateContextAttribsARB") 50 | return {}, false 51 | } 52 | 53 | if win32.wglSwapIntervalEXT == nil { 54 | log.error("Failed fetching wglSwapIntervalEXT") 55 | return {}, false 56 | } 57 | 58 | pixel_format_ilist := [?]i32 { 59 | win32.WGL_DRAW_TO_WINDOW_ARB, 1, 60 | win32.WGL_SUPPORT_OPENGL_ARB, 1, 61 | win32.WGL_DOUBLE_BUFFER_ARB, 1, 62 | win32.WGL_PIXEL_TYPE_ARB, win32.WGL_TYPE_RGBA_ARB, 63 | win32.WGL_COLOR_BITS_ARB, 32, 64 | win32.WGL_DEPTH_BITS_ARB, 24, 65 | win32.WGL_STENCIL_BITS_ARB, 8, 66 | 0, 67 | } 68 | 69 | pixel_format: i32 70 | num_formats: u32 71 | 72 | valid_pixel_format := win32.wglChoosePixelFormatARB(hdc, raw_data(pixel_format_ilist[:]), 73 | nil, 1, &pixel_format, &num_formats) 74 | 75 | if !valid_pixel_format { 76 | return {}, false 77 | } 78 | 79 | win32.SetPixelFormat(hdc, pixel_format, nil) 80 | ctx := win32.wglCreateContextAttribsARB(hdc, nil, nil) 81 | win32.wglMakeCurrent(hdc, ctx) 82 | win32.wglSwapIntervalEXT(1) 83 | return ctx, true 84 | } 85 | 86 | _gl_destroy_context :: proc(ctx: GL_Context) { 87 | win32.wglDeleteContext(ctx) 88 | } 89 | 90 | _gl_load_procs :: proc() { 91 | gl.load_up_to(3, 3, win32.gl_set_proc_address) 92 | } 93 | 94 | _gl_present :: proc(window_handle: Window_Handle) { 95 | hdc := win32.GetWindowDC(win32.HWND(window_handle)) 96 | win32.SwapBuffers(hdc) 97 | } -------------------------------------------------------------------------------- /examples/gamepad/gamepad.odin: -------------------------------------------------------------------------------- 1 | package karl2d_gamepad_example 2 | 3 | import k2 "../.." 4 | import "core:fmt" 5 | import "core:log" 6 | 7 | gamepad_demo :: proc(gamepad: k2.Gamepad_Index, offset: k2.Vec2) { 8 | if !k2.is_gamepad_active(gamepad) { 9 | title := fmt.tprintf("Gamepad: %v (not connected)", gamepad + 1) 10 | ts := k2.measure_text(title, 30) 11 | k2.draw_text(title, offset + {250, 60} - {ts.x/2, 0}, 30, k2.WHITE) 12 | return 13 | } 14 | 15 | title := fmt.tprintf("Gamepad: %v", gamepad + 1) 16 | ts := k2.measure_text(title, 30) 17 | k2.draw_text(title, offset + {250, 60} - {ts.x/2, 0}, 30, k2.WHITE) 18 | 19 | button_color :: proc( 20 | gamepad: k2.Gamepad_Index, 21 | button: k2.Gamepad_Button, 22 | active := k2.WHITE, 23 | inactive := k2.GRAY, 24 | ) -> k2.Color { 25 | return k2.gamepad_button_is_held(gamepad, button) ? active : inactive 26 | } 27 | 28 | g := gamepad 29 | o := offset 30 | k2.draw_circle(o + {120, 120}, 10, button_color(g, .Left_Face_Up)) 31 | k2.draw_circle(o + {120, 160}, 10, button_color(g, .Left_Face_Down)) 32 | k2.draw_circle(o + {100, 140}, 10, button_color(g, .Left_Face_Left)) 33 | k2.draw_circle(o + {140, 140}, 10, button_color(g, .Left_Face_Right)) 34 | 35 | k2.draw_circle(o + {320+50, 120}, 10, button_color(g, .Right_Face_Up)) 36 | k2.draw_circle(o + {320+50, 160}, 10, button_color(g, .Right_Face_Down)) 37 | k2.draw_circle(o + {300+50, 140}, 10, button_color(g, .Right_Face_Left)) 38 | k2.draw_circle(o + {340+50, 140}, 10, button_color(g, .Right_Face_Right)) 39 | 40 | k2.draw_rect_vec(o + {250 - 30, 140}, {20, 10}, button_color(g, .Middle_Face_Left)) 41 | k2.draw_rect_vec(o + {250 + 10, 140}, {20, 10}, button_color(g, .Middle_Face_Right)) 42 | 43 | left_stick := k2.Vec2 { 44 | k2.get_gamepad_axis(gamepad, .Left_Stick_X), 45 | k2.get_gamepad_axis(gamepad, .Left_Stick_Y), 46 | } 47 | 48 | right_stick := k2.Vec2 { 49 | k2.get_gamepad_axis(gamepad, .Right_Stick_X), 50 | k2.get_gamepad_axis(gamepad, .Right_Stick_Y), 51 | } 52 | 53 | left_trigger := k2.get_gamepad_axis(gamepad, .Left_Trigger) 54 | right_trigger := k2.get_gamepad_axis(gamepad, .Right_Trigger) 55 | 56 | k2.set_gamepad_vibration(gamepad, left_trigger, right_trigger) 57 | 58 | k2.draw_rect_vec(o + {80, 50}, {20, 10}, button_color(g, .Left_Shoulder)) 59 | k2.draw_rect_vec(o + {50, 50} + {0, left_trigger * 20}, {20, 10}, button_color(g, .Left_Trigger, k2.WHITE, k2.GRAY)) 60 | 61 | k2.draw_rect_vec(o + {420, 50}, {20, 10}, button_color(g, .Right_Shoulder)) 62 | k2.draw_rect_vec(o + {450, 50} + {0, right_trigger * 20}, {20, 10}, button_color(g, .Right_Trigger, k2.WHITE, k2.GRAY)) 63 | k2.draw_circle(o + {200, 200} + 20 * left_stick, 20, button_color(g, .Left_Stick_Press, k2.WHITE, k2.GRAY)) 64 | k2.draw_circle(o + {300, 200} + 20 * right_stick, 20, button_color(g, .Right_Stick_Press, k2.WHITE, k2.GRAY)) 65 | } 66 | 67 | main :: proc() { 68 | context.logger = log.create_console_logger() 69 | k2.init(1000, 600, "Karl2D Gamepad Demo") 70 | k2.set_window_position(300, 100) 71 | 72 | for !k2.shutdown_wanted() { 73 | k2.process_events() 74 | k2.clear(k2.BLACK) 75 | 76 | gamepad_demo(0, {0, 0}) 77 | gamepad_demo(1, {500, 0}) 78 | gamepad_demo(2, {0, 300}) 79 | gamepad_demo(3, {500, 300}) 80 | 81 | k2.present() 82 | free_all(context.temp_allocator) 83 | } 84 | 85 | k2.shutdown() 86 | } -------------------------------------------------------------------------------- /examples/box2d/karl2d_box2d.odin: -------------------------------------------------------------------------------- 1 | package karl2d_box2d_example 2 | 3 | import b2 "vendor:box2d" 4 | import k2 "../.." 5 | import "core:math" 6 | import "core:time" 7 | import "core:log" 8 | 9 | create_box :: proc(world_id: b2.WorldId, pos: b2.Vec2) -> b2.BodyId{ 10 | body_def := b2.DefaultBodyDef() 11 | body_def.type = .dynamicBody 12 | body_def.position = pos 13 | body_id := b2.CreateBody(world_id, body_def) 14 | 15 | shape_def := b2.DefaultShapeDef() 16 | shape_def.density = 1 17 | shape_def.material.friction = 0.3 18 | 19 | box := b2.MakeBox(20, 20) 20 | box_def := b2.DefaultShapeDef() 21 | _ = b2.CreatePolygonShape(body_id, box_def, box) 22 | 23 | return body_id 24 | } 25 | 26 | main :: proc() { 27 | context.logger = log.create_console_logger() 28 | k2.init(1280, 720, "Karl2D + Box2D example") 29 | 30 | b2.SetLengthUnitsPerMeter(40) 31 | world_def := b2.DefaultWorldDef() 32 | world_def.gravity = b2.Vec2{0, -900} 33 | world_id := b2.CreateWorld(world_def) 34 | defer b2.DestroyWorld(world_id) 35 | 36 | ground := k2.Rect { 37 | 0, 600, 38 | 1280, 120, 39 | } 40 | 41 | ground_body_def := b2.DefaultBodyDef() 42 | ground_body_def.position = b2.Vec2{ground.x, -ground.y-ground.h} 43 | ground_body_id := b2.CreateBody(world_id, ground_body_def) 44 | 45 | ground_box := b2.MakeBox(ground.w, ground.h) 46 | ground_shape_def := b2.DefaultShapeDef() 47 | _ = b2.CreatePolygonShape(ground_body_id, ground_shape_def, ground_box) 48 | 49 | bodies: [dynamic]b2.BodyId 50 | 51 | px: f32 = 400 52 | py: f32 = -400 53 | 54 | num_per_row := 10 55 | num_in_row := 0 56 | 57 | for _ in 0..<50 { 58 | b := create_box(world_id, {px, py}) 59 | append(&bodies, b) 60 | num_in_row += 1 61 | 62 | if num_in_row == num_per_row { 63 | py += 30 64 | px = 200 65 | num_per_row -= 1 66 | num_in_row = 0 67 | } 68 | 69 | px += 30 70 | } 71 | 72 | body_def := b2.DefaultBodyDef() 73 | body_def.type = .dynamicBody 74 | body_def.position = b2.Vec2{0, 4} 75 | body_id := b2.CreateBody(world_id, body_def) 76 | 77 | shape_def := b2.DefaultShapeDef() 78 | shape_def.density = 1000 79 | shape_def.material.friction = 0.3 80 | 81 | circle: b2.Circle 82 | circle.radius = 40 83 | _ = b2.CreateCircleShape(body_id, shape_def, circle) 84 | 85 | time_step: f32 = 1.0 / 60 86 | sub_steps: i32 = 4 87 | 88 | prev_time := time.now() 89 | 90 | time_acc: f32 91 | 92 | for !k2.shutdown_wanted() { 93 | cur_time := time.now() 94 | dt := f32(time.duration_seconds(time.diff(prev_time, cur_time))) 95 | prev_time = cur_time 96 | 97 | time_acc += dt 98 | k2.process_events() 99 | k2.clear(k2.BLACK) 100 | 101 | k2.draw_rect(ground, k2.RL_RED) 102 | 103 | pos := k2.get_mouse_position() 104 | 105 | b2.Body_SetTransform(body_id, {pos.x, -pos.y}, {}) 106 | 107 | for time_acc >= time_step { 108 | b2.World_Step(world_id, time_step, sub_steps) 109 | time_acc -= time_step 110 | } 111 | 112 | for b in bodies { 113 | position := b2.Body_GetPosition(b) 114 | r := b2.Body_GetRotation(b) 115 | a := math.atan2(r.s, r.c) 116 | // Y position is flipped because raylib has Y down and box2d has Y up. 117 | k2.draw_rect_ex({position.x, -position.y, 40, 40}, {20, 20}, a*(180/3.14), k2.RL_YELLOW) 118 | } 119 | 120 | k2.draw_circle(pos, 40, k2.RL_MAGENTA) 121 | k2.present() 122 | 123 | free_all(context.temp_allocator) 124 | } 125 | 126 | k2.shutdown() 127 | } -------------------------------------------------------------------------------- /examples/raylib_ports/2d_camera/2d_camera.odin: -------------------------------------------------------------------------------- 1 | // This is a port of https://github.com/raysan5/raylib/blob/master/examples/core/core_2d_camera.c 2 | 3 | package raylib_example_2d_camera 4 | 5 | import k2 "../../.." 6 | import "core:math/rand" 7 | import "core:math" 8 | import "core:log" 9 | 10 | MAX_BUILDINGS :: 100 11 | SCREEN_WIDTH :: 800 12 | SCREEN_HEIGHT :: 450 13 | 14 | main :: proc() { 15 | context.logger = log.create_console_logger() 16 | k2.init(SCREEN_WIDTH, SCREEN_HEIGHT, "Karl2D: 2d camera (raylib [core] example - 2d camera)") 17 | k2.set_window_position(500, 100) 18 | 19 | player := k2.Rect { 400, 280, 40, 40 } 20 | buildings: [MAX_BUILDINGS]k2.Rect 21 | building_colors: [MAX_BUILDINGS]k2.Color 22 | 23 | spacing: f32 24 | 25 | for i in 0.. 40 { camera.rotation = 40 } 63 | else if camera.rotation < -40 { camera.rotation = -40 } 64 | 65 | camera.zoom = math.exp(math.log(camera.zoom, math.E) + f32(k2.get_mouse_wheel_delta() * 0.1)) 66 | 67 | if camera.zoom > 3 { camera.zoom = 3 } 68 | else if camera.zoom < 0.1 { camera.zoom = 0.1 } 69 | 70 | if k2.key_went_down(.R) { 71 | camera.zoom = 1 72 | camera.rotation = 0 73 | } 74 | 75 | k2.clear({ 245, 245, 245, 255 }) 76 | k2.set_camera(camera) 77 | k2.draw_rect({-6000, 320, 13000, 8000}, k2.RL_DARKGRAY) 78 | 79 | for i in 0.. make it look crisp by disabling filtering on "pixel fonts" 52 | * next stage: Look into something more fancy than just loading bitmaps. What can we do? 53 | * bunnymark 54 | * win32: Resizable window 55 | * Flashing textures in Abyss -- Better now but still flashes when you use nose... Check the "odd_frame" stuff in d3d backend 56 | * Is the 1/zoom in set_camera wrong? Is the matrix multiply order wrong? Hmmmm... 57 | * Fix the depedency on D3D stuff so we can move load_shader etc 58 | * Shaders: Basic loading 59 | * Shaders: Constants that you can set 60 | * Shaders: Dynamic vertex creation 61 | * Shaders: Feed extra vertex field values using some kind of context 62 | * Do we need one for all corners of a rect or possibilty to supply different value for the different corners? 63 | * Group set_tex, camera etc into a section of things that cause a render batch dispatch when changed. 64 | * Make a texture for drawing a rectangle and remove the hack in `shader.hlsl` 65 | * Load textures and somehow bind to shader -- split draw calls on texture switch -- needs a start of a batch system. 66 | * Make 0, 0 be at top left (should vertex data be flipped, or is it a transformation thingy?) 67 | * Construct vertex buffer from k2.draw_blabla calls. Do we need index buffer? 🤷‍ 68 | * Organize the d3d11 things neatly. It's just a hack right now! 69 | * enable debug layers 70 | * asserting on hresult and checking errors 71 | * clean up on shutdown 72 | -------------------------------------------------------------------------------- /.sublime/karl2d.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "file_regex": "^(.+)\\(([0-9]+):([0-9]+)\\) (.+)$", 6 | "name": "Karl2D", 7 | "variants": 8 | [ 9 | { 10 | "name": "minimal", 11 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=d3d11", 12 | "working_dir": "$project_path/../examples/minimal" 13 | }, 14 | { 15 | "name": "minimal (gl)", 16 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=gl", 17 | "working_dir": "$project_path/../examples/minimal" 18 | }, 19 | { 20 | "name": "minimal (web)", 21 | "shell_cmd": "odin build . -vet -strict-style -keep-executable -debug -target:js_wasm32", 22 | "working_dir": "$project_path/../examples/minimal" 23 | }, 24 | { 25 | "name": "fonts", 26 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug", 27 | "working_dir": "$project_path/../examples/fonts" 28 | }, 29 | { 30 | "name": "render_texture", 31 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug", 32 | "working_dir": "$project_path/../examples/render_texture" 33 | }, 34 | { 35 | "name": "render_texture (gl)", 36 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=gl", 37 | "working_dir": "$project_path/../examples/render_texture" 38 | }, 39 | { 40 | "name": "multitexture", 41 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=d3d11", 42 | "working_dir": "$project_path/../examples/multitexture" 43 | }, 44 | { 45 | "name": "multitexture (gl)", 46 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=gl", 47 | "working_dir": "$project_path/../examples/multitexture" 48 | }, 49 | { 50 | "name": "snake", 51 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug", 52 | "working_dir": "$project_path/../examples/snake" 53 | }, 54 | { 55 | "name": "box2d", 56 | "shell_cmd": "odin run . -vet -strict-style -keep-executable", 57 | "working_dir": "$project_path/../examples/box2d" 58 | }, 59 | { 60 | "name": "premultiplied_alpha", 61 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=d3d11", 62 | "working_dir": "$project_path/../examples/premultiplied_alpha" 63 | }, 64 | { 65 | "name": "premultiplied_alpha (gl)", 66 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug -define:KARL2D_RENDER_BACKEND=gl", 67 | "working_dir": "$project_path/../examples/premultiplied_alpha" 68 | }, 69 | { 70 | "name": "raylib_ports/2d_camera", 71 | "shell_cmd": "odin run . -vet -strict-style -keep-executable -debug", 72 | "working_dir": "$project_path/../examples/raylib_ports/2d_camera" 73 | }, 74 | { 75 | "name": "raylib_ports/shaders_texture_waves", 76 | "shell_cmd": "odin run . -keep-executable -debug", 77 | "working_dir": "$project_path/../examples/raylib_ports/shaders_texture_waves" 78 | }, 79 | { 80 | "name": "bunnymark", 81 | "shell_cmd": "odin run . -vet -strict-style -no-bounds-check -keep-executable", 82 | "working_dir": "$project_path/../examples/raylib_ports/bunnymark" 83 | }, 84 | { 85 | "name": "gamepad", 86 | "shell_cmd": "odin run . -vet -strict-style -keep-executable", 87 | "working_dir": "$project_path/../examples/gamepad" 88 | }, 89 | { 90 | "name": "api_doc_builder", 91 | "shell_cmd": "odin run api_doc_builder", 92 | "working_dir": "$project_path/.." 93 | }, 94 | ] 95 | } 96 | ], 97 | "folders": 98 | [ 99 | { 100 | "path": "C:\\Projects\\karl2d" 101 | }, 102 | { 103 | "path": "C:\\SDK\\Odin\\base" 104 | }, 105 | { 106 | "path": "C:\\SDK\\Odin\\core" 107 | }, 108 | { 109 | "path": "C:\\SDK\\Odin\\vendor\\fontstash" 110 | }, 111 | { 112 | "path": "C:\\SDK\\Odin\\vendor\\OpenGL" 113 | }, 114 | { 115 | "path": "C:\\Projects\\Scraps\\raylib\\src" 116 | }, 117 | { 118 | "path": "C:\\SDK\\Odin\\vendor\\directx\\d3d11" 119 | }, 120 | { 121 | "path": "C:\\SDK\\Odin\\vendor\\directx\\dxgi" 122 | }, 123 | { 124 | "path": "C:\\SDK\\Odin\\vendor\\directx\\d3d_compiler" 125 | }, 126 | { 127 | "path": "C:\\SDK\\Odin\\vendor\\directx\\dxc" 128 | }, 129 | { 130 | "path": "C:\\SDK\\Odin\\vendor\\wasm\\WebGL" 131 | }, 132 | { 133 | "path": "C:\\SDK\\Odin\\vendor\\box2d" 134 | } 135 | ], 136 | "settings": 137 | { 138 | "LSP": 139 | { 140 | "odin": 141 | { 142 | "enabled": true 143 | } 144 | }, 145 | "auto_complete": false 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /examples/snake/snake.odin: -------------------------------------------------------------------------------- 1 | package snake 2 | 3 | import k2 "../.." 4 | import "core:math" 5 | import "core:fmt" 6 | import "core:time" 7 | import "core:math/rand" 8 | import "base:intrinsics" 9 | import "core:log" 10 | import "core:mem" 11 | 12 | _ :: mem 13 | 14 | WINDOW_SIZE :: 1000 15 | GRID_WIDTH :: 20 16 | CELL_SIZE :: 16 17 | CANVAS_SIZE :: GRID_WIDTH*CELL_SIZE 18 | TICK_RATE :: 0.13 19 | Vec2i :: [2]int 20 | MAX_SNAKE_LENGTH :: GRID_WIDTH*GRID_WIDTH 21 | 22 | snake: [MAX_SNAKE_LENGTH]Vec2i 23 | snake_length: int 24 | tick_timer: f32 = TICK_RATE 25 | move_direction: Vec2i 26 | game_over: bool 27 | food_pos: Vec2i 28 | 29 | place_food :: proc() { 30 | occupied: [GRID_WIDTH][GRID_WIDTH]bool 31 | 32 | for i in 0.. 0 { 47 | random_cell_index := rand.int31_max(i32(len(free_cells))) 48 | food_pos = free_cells[random_cell_index] 49 | } 50 | 51 | } 52 | 53 | restart :: proc() { 54 | start_head_pos := Vec2i { GRID_WIDTH / 2, GRID_WIDTH / 2 } 55 | snake[0] = start_head_pos 56 | snake[1] = start_head_pos - {0, 1} 57 | snake[2] = start_head_pos - {0, 2} 58 | snake_length = 3 59 | move_direction = {0, 1} 60 | game_over = false 61 | place_food() 62 | } 63 | 64 | main :: proc() { 65 | context.logger = log.create_console_logger() 66 | 67 | when ODIN_DEBUG { 68 | track: mem.Tracking_Allocator 69 | mem.tracking_allocator_init(&track, context.allocator) 70 | context.allocator = mem.tracking_allocator(&track) 71 | 72 | defer { 73 | if len(track.allocation_map) > 0 { 74 | for _, entry in track.allocation_map { 75 | fmt.eprintf("%v leaked: %v bytes\n", entry.location, entry.size) 76 | } 77 | } 78 | mem.tracking_allocator_destroy(&track) 79 | } 80 | } 81 | 82 | k2.init(WINDOW_SIZE, WINDOW_SIZE, "Snake") 83 | k2.set_window_position(300, 300) 84 | 85 | SHADER_SOURCE :: #load("shader.hlsl") 86 | 87 | shader := k2.load_shader_from_memory(SHADER_SOURCE, SHADER_SOURCE, { 88 | .RG_32_Float, 89 | .RG_32_Float, 90 | .RGBA_8_Norm, 91 | .RG_32_Float, 92 | }) 93 | 94 | prev_time := time.now() 95 | 96 | restart() 97 | 98 | food_sprite := k2.load_texture_from_file("food.png") 99 | head_sprite := k2.load_texture_from_file("head.png") 100 | body_sprite := k2.load_texture_from_file("body.png") 101 | tail_sprite := k2.load_texture_from_file("tail.png") 102 | 103 | food_eaten_at := time.now() 104 | started_at := time.now() 105 | 106 | for !k2.shutdown_wanted() { 107 | time_now := time.now() 108 | dt := f32(time.duration_seconds(time.diff(prev_time, time_now))) 109 | prev_time = time_now 110 | total_time := time.duration_seconds(time.diff(started_at, time_now)) 111 | k2.process_events() 112 | 113 | if k2.key_is_held(.Up) || k2.gamepad_button_is_held(0, .Left_Face_Up) { 114 | move_direction = {0, -1} 115 | } 116 | 117 | if k2.key_is_held(.Down) || k2.gamepad_button_is_held(0, .Left_Face_Down) { 118 | move_direction = {0, 1} 119 | } 120 | 121 | if k2.key_is_held(.Left) || k2.gamepad_button_is_held(0, .Left_Face_Left) { 122 | move_direction = {-1, 0} 123 | } 124 | 125 | if k2.key_is_held(.Right) || k2.gamepad_button_is_held(0, .Left_Face_Right) { 126 | move_direction = {1, 0} 127 | } 128 | 129 | if game_over { 130 | if k2.key_went_down(.Enter) { 131 | restart() 132 | } 133 | } else { 134 | tick_timer -= dt 135 | } 136 | 137 | if tick_timer <= 0 { 138 | next_part_pos := snake[0] 139 | snake[0] += move_direction 140 | head_pos := snake[0] 141 | 142 | if head_pos.x < 0 || head_pos.y < 0 || head_pos.x >= GRID_WIDTH || head_pos.y >= GRID_WIDTH { 143 | game_over = true 144 | } 145 | 146 | for i in 1.. 1 { 181 | k2.override_shader_input(shader, 3, k2.Vec2{f32(math.cos(total_time*100)*4), f32(math.sin(total_time*120 + 3)*4)}) 182 | } 183 | 184 | k2.draw_texture(food_sprite, {f32(food_pos.x), f32(food_pos.y)}*CELL_SIZE) 185 | 186 | k2.override_shader_input(shader, 3, nil) 187 | 188 | for i in 0.. pointer 31 | if h2e := hm.get(&entities, h2); h2e != nil { 32 | h2e.pos.y = 123 33 | } 34 | 35 | // Will remove this entity, leaving an unused slot 36 | hm.remove(&entities, h1) 37 | 38 | // Will reuse the slot h1 used 39 | h3 := hm.add(&entities, Entity { pos = { 1, 2 } }) 40 | 41 | // Iterate. You can also use `for e in hm.items {}` and skip any item where 42 | // `e.handle.idx == 0`. The iterator does that automatically. There's also 43 | // `skip` procedure in this package that check `e.handle.idx == 0` for you. 44 | ent_iter := hm.make_iter(&entities) 45 | for e, h in hm.iter(&ent_iter) { 46 | e.pos += { 5, 1 } 47 | } 48 | */ 49 | package handle_map_fixed 50 | 51 | import "base:intrinsics" 52 | 53 | // Returned from the `add` proc. Store these as permanent references to items in 54 | // the handle map. You can resolve the handle to a pointer using the `get` proc. 55 | Handle :: struct { 56 | // index into `items` array of the `Handle_Map` struct. 57 | idx: u32, 58 | 59 | // When using the `get` proc, this will be matched to the `gen` on the item 60 | // in the handle map. The handle is only valid if they match. If they don't 61 | // match, then it means that the slot in the handle map has been reused. 62 | gen: u32, 63 | } 64 | 65 | Handle_Map :: struct($T: typeid, $HT: typeid, $N: int) { 66 | // Each item must have a field `handle` of type `HT`. 67 | // 68 | // There's always a "dummy element" at index 0. This way, a Handle with 69 | // `idx == 0` means "no Handle". This means that you actually have `N - 1` 70 | // items available. 71 | items: [N]T, 72 | 73 | // How much of `items` that is in use. 74 | num_items: u32, 75 | 76 | // The index of the first unused element in `items`. At this index in 77 | // `unused_items` you'll find the next-next unused index. Used by `add` and 78 | // originally set by `remove`. 79 | next_unused: u32, 80 | 81 | // An element in this array that is non-zero means the thing at that index 82 | // in `items` is unused. The non-zero number is the index of the next unused 83 | // item. This forms a linked series of indices. The series starts with 84 | // `next_unused`. 85 | unused_items: [N]u32, 86 | 87 | // Only used for making it possible to quickly calculate the number of valid 88 | // elements. 89 | num_unused: u32, 90 | } 91 | 92 | // Clears the handle map using `mem_zero`. It doesn't do `m^ = {}` because that 93 | // may blow the stack for a handle map with very big `N` 94 | clear :: proc(m: ^Handle_Map($T, $HT, $N)) { 95 | intrinsics.mem_zero(m, size_of(m^)) 96 | } 97 | 98 | // Add a value of type `T` to the handle map. Returns a handle you can use as a 99 | // permanent reference. 100 | // 101 | // Will reuse the item at `next_unused` if that value is non-zero. 102 | // 103 | // Second return value is `false` if the handle-based map is full. 104 | add :: proc(m: ^Handle_Map($T, $HT, $N), v: T) -> (HT, bool) #optional_ok { 105 | v := v 106 | 107 | if m.next_unused != 0 { 108 | idx := m.next_unused 109 | item := &m.items[idx] 110 | m.next_unused = m.unused_items[idx] 111 | m.unused_items[idx] = 0 112 | gen := item.handle.gen 113 | item^ = v 114 | item.handle.idx = u32(idx) 115 | item.handle.gen = gen + 1 116 | m.num_unused -= 1 117 | return item.handle, true 118 | } 119 | 120 | // We always have a "dummy item" at index zero. This is because handle.idx 121 | // being zero means "no item", so we can't use that slot for anything. 122 | if m.num_items == 0 { 123 | m.items[0] = {} 124 | m.num_items += 1 125 | } 126 | 127 | if m.num_items == len(m.items) { 128 | return {}, false 129 | } 130 | 131 | item := &m.items[m.num_items] 132 | item^ = v 133 | item.handle.idx = u32(m.num_items) 134 | item.handle.gen = 1 135 | m.num_items += 1 136 | return item.handle, true 137 | } 138 | 139 | // Resolve a handle to a pointer of type `^T`. The pointer is stable since the 140 | // handle map uses a fixed array. But you should _not_ store the pointer 141 | // permanently. The item may get reused if any part of your program destroys and 142 | // reuses that slot. Only store handles permanently and temporarily resolve them 143 | // into pointers as needed. 144 | get :: proc(m: ^Handle_Map($T, $HT, $N), h: HT) -> ^T { 145 | if h.idx <= 0 || h.idx >= m.num_items { 146 | return nil 147 | } 148 | 149 | if item := &m.items[h.idx]; item.handle == h { 150 | return item 151 | } 152 | 153 | return nil 154 | } 155 | 156 | // Remove an item from the handle map. You choose which item by passing a handle 157 | // to this proc. The item is not really destroyed, rather its index is just 158 | // set on `m.next_unused`. Also, the item's `handle.idx` is set to zero, this 159 | // is used by the `iter` proc in order to skip that item when iterating. 160 | remove :: proc(m: ^Handle_Map($T, $HT, $N), h: HT) { 161 | if h.idx <= 0 || h.idx >= m.num_items { 162 | return 163 | } 164 | 165 | if item := &m.items[h.idx]; item.handle == h { 166 | m.unused_items[h.idx] = m.next_unused 167 | m.next_unused = h.idx 168 | m.num_unused += 1 169 | item.handle.idx = 0 170 | } 171 | } 172 | 173 | // Tells you if a handle maps to a valid item. This is done by checking if the 174 | // handle on the item is the same as the passed handle. 175 | valid :: proc(m: Handle_Map($T, $HT, $N), h: HT) -> bool { 176 | return h.idx > 0 && h.idx < m.num_items && m.items[h.idx].handle == h 177 | } 178 | 179 | // Tells you how many valid items there are in the handle map. 180 | num_used :: proc(m: Handle_Map($T, $HT, $N)) -> int { 181 | return int(m.num_items - m.num_unused) 182 | } 183 | 184 | // The maximum number of items the handle map can contain. 185 | cap :: proc(m: Handle_Map($T, $HT, $N)) -> int { 186 | return N 187 | } 188 | 189 | // For iterating a handle map. Create using `make_iter`. 190 | Handle_Map_Iterator :: struct($T: typeid, $HT: typeid, $N: int) { 191 | m: ^Handle_Map(T, HT, N), 192 | index: u32, 193 | } 194 | 195 | // Create an iterator. Use with `iter` to do the actual iteration. 196 | make_iter :: proc(m: ^Handle_Map($T, $HT, $N)) -> Handle_Map_Iterator(T, HT, N) { 197 | return { m = m, index = 1 } 198 | } 199 | 200 | // Iterate over the handle map. Skips unused slots, meaning that it skips slots 201 | // with handle.idx == 0. 202 | // 203 | // Usage: 204 | // my_iter := hm.make_iter(&my_handle_map) 205 | // for e in hm.iter(&my_iter) {} 206 | // 207 | // Instead of using an iterator you can also loop over `items` and check if 208 | // `item.handle.idx == 0` and in that case skip that item. 209 | iter :: proc(it: ^Handle_Map_Iterator($T, $HT, $N)) -> (val: ^T, h: HT, cond: bool) { 210 | for _ in it.index.. bool { 230 | return e.handle.idx == 0 231 | } -------------------------------------------------------------------------------- /window_win32.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | #+private file 3 | 4 | package karl2d 5 | 6 | @(private="package") 7 | WINDOW_INTERFACE_WIN32 :: Window_Interface { 8 | state_size = win32_state_size, 9 | init = win32_init, 10 | shutdown = win32_shutdown, 11 | window_handle = win32_window_handle, 12 | process_events = win32_process_events, 13 | get_events = win32_get_events, 14 | get_width = win32_get_width, 15 | get_height = win32_get_height, 16 | clear_events = win32_clear_events, 17 | set_position = win32_set_position, 18 | set_size = win32_set_size, 19 | get_window_scale = win32_get_window_scale, 20 | set_flags = win32_set_flags, 21 | is_gamepad_active = win32_is_gamepad_active, 22 | get_gamepad_axis = win32_get_gamepad_axis, 23 | set_gamepad_vibration = win32_set_gamepad_vibration, 24 | 25 | set_internal_state = win32_set_internal_state, 26 | } 27 | 28 | import win32 "core:sys/windows" 29 | import "base:runtime" 30 | 31 | win32_state_size :: proc() -> int { 32 | return size_of(Win32_State) 33 | } 34 | 35 | win32_init :: proc(window_state: rawptr, window_width: int, window_height: int, window_title: string, 36 | flags: Window_Flags, allocator: runtime.Allocator) { 37 | assert(window_state != nil) 38 | s = (^Win32_State)(window_state) 39 | s.allocator = allocator 40 | s.events = make([dynamic]Window_Event, allocator) 41 | s.width = window_width 42 | s.height = window_height 43 | s.custom_context = context 44 | 45 | win32.SetProcessDpiAwarenessContext(win32.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) 46 | win32.SetProcessDPIAware() 47 | CLASS_NAME :: "karl2d" 48 | instance := win32.HINSTANCE(win32.GetModuleHandleW(nil)) 49 | 50 | cls := win32.WNDCLASSW { 51 | style = win32.CS_OWNDC, 52 | lpfnWndProc = window_proc, 53 | lpszClassName = CLASS_NAME, 54 | hInstance = instance, 55 | hCursor = win32.LoadCursorA(nil, win32.IDC_ARROW), 56 | } 57 | 58 | win32.RegisterClassW(&cls) 59 | 60 | r: win32.RECT 61 | r.right = i32(window_width) 62 | r.bottom = i32(window_height) 63 | 64 | s.flags = flags 65 | 66 | style := style_from_flags(flags) 67 | 68 | win32.AdjustWindowRect(&r, style, false) 69 | 70 | hwnd := win32.CreateWindowW(CLASS_NAME, 71 | win32.utf8_to_wstring(window_title), 72 | style, 73 | 100, 10, r.right - r.left, r.bottom - r.top, 74 | nil, nil, instance, nil, 75 | ) 76 | 77 | win32.XInputEnable(true) 78 | 79 | assert(hwnd != nil, "Failed creating window") 80 | 81 | s.hwnd = hwnd 82 | } 83 | 84 | win32_shutdown :: proc() { 85 | delete(s.events) 86 | win32.DestroyWindow(s.hwnd) 87 | } 88 | 89 | win32_window_handle :: proc() -> Window_Handle { 90 | return Window_Handle(s.hwnd) 91 | } 92 | 93 | win32_process_events :: proc() { 94 | msg: win32.MSG 95 | 96 | for win32.PeekMessageW(&msg, nil, 0, 0, win32.PM_REMOVE) { 97 | win32.TranslateMessage(&msg) 98 | win32.DispatchMessageW(&msg) 99 | } 100 | 101 | for gamepad in 0..<4 { 102 | gp_event: win32.XINPUT_KEYSTROKE 103 | 104 | for win32.XInputGetKeystroke(win32.XUSER(gamepad), 0, &gp_event) == .SUCCESS { 105 | button: Maybe(Gamepad_Button) 106 | 107 | #partial switch gp_event.VirtualKey { 108 | case .DPAD_UP: button = .Left_Face_Up 109 | case .DPAD_DOWN: button = .Left_Face_Down 110 | case .DPAD_LEFT: button = .Left_Face_Left 111 | case .DPAD_RIGHT: button = .Left_Face_Right 112 | 113 | case .Y: button = .Right_Face_Up 114 | case .A: button = .Right_Face_Down 115 | case .X: button = .Right_Face_Left 116 | case .B: button = .Right_Face_Right 117 | 118 | case .LSHOULDER: button = .Left_Shoulder 119 | case .LTRIGGER: button = .Left_Trigger 120 | 121 | case .RSHOULDER: button = .Right_Shoulder 122 | case .RTRIGGER: button = .Right_Trigger 123 | 124 | case .BACK: button = .Middle_Face_Left 125 | 126 | // Not sure you can get the "middle button" with XInput (the one that goe to dashboard) 127 | 128 | case .START: button = .Middle_Face_Right 129 | 130 | case .LTHUMB_PRESS: button = .Left_Stick_Press 131 | case .RTHUMB_PRESS: button = .Right_Stick_Press 132 | } 133 | 134 | b := button.? or_continue 135 | evt: Window_Event 136 | 137 | if .KEYDOWN in gp_event.Flags { 138 | evt = Window_Event_Gamepad_Button_Went_Down { 139 | gamepad = gamepad, 140 | button = b, 141 | } 142 | } else if .KEYUP in gp_event.Flags { 143 | evt = Window_Event_Gamepad_Button_Went_Up { 144 | gamepad = gamepad, 145 | button = b, 146 | } 147 | } 148 | 149 | if evt != nil { 150 | append(&s.events, evt) 151 | } 152 | } 153 | } 154 | 155 | } 156 | 157 | win32_get_events :: proc() -> []Window_Event { 158 | return s.events[:] 159 | } 160 | 161 | win32_get_width :: proc() -> int { 162 | return s.width 163 | } 164 | 165 | win32_get_height :: proc() -> int { 166 | return s.height 167 | } 168 | 169 | win32_clear_events :: proc() { 170 | runtime.clear(&s.events) 171 | } 172 | 173 | win32_set_position :: proc(x: int, y: int) { 174 | // TODO: Does x, y respect monitor DPI? 175 | 176 | win32.SetWindowPos( 177 | s.hwnd, 178 | {}, 179 | i32(x), 180 | i32(y), 181 | 0, 182 | 0, 183 | win32.SWP_NOACTIVATE | win32.SWP_NOZORDER | win32.SWP_NOSIZE, 184 | ) 185 | } 186 | 187 | win32_set_size :: proc(w, h: int) { 188 | win32.SetWindowPos( 189 | s.hwnd, 190 | {}, 191 | 0, 192 | 0, 193 | i32(w), 194 | i32(h), 195 | win32.SWP_NOACTIVATE | win32.SWP_NOZORDER | win32.SWP_NOMOVE, 196 | ) 197 | } 198 | 199 | win32_get_window_scale :: proc() -> f32 { 200 | return f32(win32.GetDpiForWindow(s.hwnd))/96.0 201 | } 202 | 203 | win32_set_flags :: proc(flags: Window_Flags) { 204 | s.flags = flags 205 | style := style_from_flags(flags) 206 | win32.SetWindowLongW(s.hwnd, win32.GWL_STYLE, i32(style)) 207 | } 208 | 209 | win32_is_gamepad_active :: proc(gamepad: int) -> bool { 210 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 211 | return false 212 | } 213 | 214 | gp_state: win32.XINPUT_STATE 215 | return win32.XInputGetState(win32.XUSER(gamepad), &gp_state) == .SUCCESS 216 | } 217 | 218 | win32_get_gamepad_axis :: proc(gamepad: int, axis: Gamepad_Axis) -> f32 { 219 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 220 | return 0 221 | } 222 | 223 | gp_state: win32.XINPUT_STATE 224 | if win32.XInputGetState(win32.XUSER(gamepad), &gp_state) == .SUCCESS { 225 | gp := gp_state.Gamepad 226 | 227 | // Numbers from https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad 228 | STICK_MAX :: 32767 229 | TRIGGER_MAX :: 255 230 | 231 | switch axis { 232 | case .Left_Stick_X: return f32(gp.sThumbLX) / STICK_MAX 233 | case .Left_Stick_Y: return -f32(gp.sThumbLY) / STICK_MAX 234 | case .Right_Stick_X: return f32(gp.sThumbRX) / STICK_MAX 235 | case .Right_Stick_Y: return -f32(gp.sThumbRY) / STICK_MAX 236 | case .Left_Trigger: return f32(gp.bLeftTrigger) / TRIGGER_MAX 237 | case .Right_Trigger: return f32(gp.bRightTrigger) / TRIGGER_MAX 238 | } 239 | } 240 | 241 | return 0 242 | } 243 | 244 | win32_set_gamepad_vibration :: proc(gamepad: int, left: f32, right: f32) { 245 | if gamepad < 0 || gamepad >= MAX_GAMEPADS { 246 | return 247 | } 248 | 249 | vib := win32.XINPUT_VIBRATION { 250 | wLeftMotorSpeed = win32.WORD(left * 65535), 251 | wRightMotorSpeed = win32.WORD(right * 65535), 252 | } 253 | 254 | win32.XInputSetState(win32.XUSER(gamepad), &vib) 255 | } 256 | 257 | win32_set_internal_state :: proc(state: rawptr) { 258 | assert(state != nil) 259 | s = (^Win32_State)(state) 260 | } 261 | 262 | Win32_State :: struct { 263 | allocator: runtime.Allocator, 264 | custom_context: runtime.Context, 265 | hwnd: win32.HWND, 266 | flags: Window_Flags, 267 | width: int, 268 | height: int, 269 | events: [dynamic]Window_Event, 270 | } 271 | 272 | style_from_flags :: proc(flags: Window_Flags) -> win32.DWORD { 273 | style := win32.WS_OVERLAPPED | win32.WS_CAPTION | win32.WS_SYSMENU | 274 | win32.WS_MINIMIZEBOX | win32.WS_MAXIMIZEBOX | win32.WS_VISIBLE | 275 | win32.CS_OWNDC 276 | 277 | if .Resizable in flags { 278 | style |= win32.WS_THICKFRAME 279 | } 280 | 281 | return style 282 | } 283 | 284 | s: ^Win32_State 285 | 286 | window_proc :: proc "stdcall" (hwnd: win32.HWND, msg: win32.UINT, wparam: win32.WPARAM, lparam: win32.LPARAM) -> win32.LRESULT { 287 | context = s.custom_context 288 | switch msg { 289 | case win32.WM_DESTROY: 290 | win32.PostQuitMessage(0) 291 | 292 | case win32.WM_CLOSE: 293 | append(&s.events, Window_Event_Close_Wanted{}) 294 | 295 | case win32.WM_KEYDOWN: 296 | key := key_from_event_params(wparam, lparam) 297 | append(&s.events, Window_Event_Key_Went_Down { 298 | key = key, 299 | }) 300 | 301 | return 0 302 | 303 | case win32.WM_KEYUP: 304 | key := key_from_event_params(wparam, lparam) 305 | append(&s.events, Window_Event_Key_Went_Up { 306 | key = key, 307 | }) 308 | 309 | return 0 310 | 311 | case win32.WM_MOUSEMOVE: 312 | x := win32.GET_X_LPARAM(lparam) 313 | y := win32.GET_Y_LPARAM(lparam) 314 | append(&s.events, Window_Event_Mouse_Move { 315 | position = {f32(x), f32(y)}, 316 | }) 317 | 318 | return 0 319 | 320 | case win32.WM_MOUSEWHEEL: 321 | delta := f32(win32.GET_WHEEL_DELTA_WPARAM(wparam))/win32.WHEEL_DELTA 322 | 323 | append(&s.events, Window_Event_Mouse_Wheel { 324 | delta = delta, 325 | }) 326 | 327 | case win32.WM_LBUTTONDOWN: 328 | append(&s.events, Window_Event_Mouse_Button_Went_Down { 329 | button = .Left, 330 | }) 331 | 332 | case win32.WM_LBUTTONUP: 333 | append(&s.events, Window_Event_Mouse_Button_Went_Up { 334 | button = .Left, 335 | }) 336 | 337 | case win32.WM_MBUTTONDOWN: 338 | append(&s.events, Window_Event_Mouse_Button_Went_Down { 339 | button = .Middle, 340 | }) 341 | 342 | case win32.WM_MBUTTONUP: 343 | append(&s.events, Window_Event_Mouse_Button_Went_Up { 344 | button = .Middle, 345 | }) 346 | 347 | case win32.WM_RBUTTONDOWN: 348 | append(&s.events, Window_Event_Mouse_Button_Went_Down { 349 | button = .Right, 350 | }) 351 | 352 | case win32.WM_RBUTTONUP: 353 | append(&s.events, Window_Event_Mouse_Button_Went_Up { 354 | button = .Right, 355 | }) 356 | 357 | case win32.WM_SIZE: 358 | width := win32.LOWORD(lparam) 359 | height := win32.HIWORD(lparam) 360 | 361 | s.width = int(width) 362 | s.height = int(height) 363 | 364 | append(&s.events, Window_Event_Resize { 365 | width = int(width), 366 | height = int(height), 367 | }) 368 | } 369 | 370 | return win32.DefWindowProcW(hwnd, msg, wparam, lparam) 371 | } 372 | 373 | key_from_event_params :: proc(wparam: win32.WPARAM, lparam: win32.LPARAM) -> Keyboard_Key{ 374 | if wparam == win32.VK_RETURN && win32.HIWORD(lparam) & win32.KF_EXTENDED != 0 { 375 | return .NP_Enter 376 | } 377 | 378 | return WIN32_VK_MAP[wparam] 379 | } 380 | 381 | WIN32_VK_MAP := [255]Keyboard_Key { 382 | win32.VK_0 = .N0, 383 | win32.VK_1 = .N1, 384 | win32.VK_2 = .N2, 385 | win32.VK_3 = .N3, 386 | win32.VK_4 = .N4, 387 | win32.VK_5 = .N5, 388 | win32.VK_6 = .N6, 389 | win32.VK_7 = .N7, 390 | win32.VK_8 = .N8, 391 | win32.VK_9 = .N9, 392 | 393 | win32.VK_A = .A, 394 | win32.VK_B = .B, 395 | win32.VK_C = .C, 396 | win32.VK_D = .D, 397 | win32.VK_E = .E, 398 | win32.VK_F = .F, 399 | win32.VK_G = .G, 400 | win32.VK_H = .H, 401 | win32.VK_I = .I, 402 | win32.VK_J = .J, 403 | win32.VK_K = .K, 404 | win32.VK_L = .L, 405 | win32.VK_M = .M, 406 | win32.VK_N = .N, 407 | win32.VK_O = .O, 408 | win32.VK_P = .P, 409 | win32.VK_Q = .Q, 410 | win32.VK_R = .R, 411 | win32.VK_S = .S, 412 | win32.VK_T = .T, 413 | win32.VK_U = .U, 414 | win32.VK_V = .V, 415 | win32.VK_W = .W, 416 | win32.VK_X = .X, 417 | win32.VK_Y = .Y, 418 | win32.VK_Z = .Z, 419 | 420 | win32.VK_OEM_7 = .Apostrophe, 421 | win32.VK_OEM_COMMA = .Comma, 422 | win32.VK_OEM_MINUS = .Minus, 423 | win32.VK_OEM_PERIOD = .Period, 424 | win32.VK_OEM_2 = .Slash, 425 | win32.VK_OEM_1 = .Semicolon, 426 | win32.VK_OEM_PLUS = .Equal, 427 | win32.VK_OEM_4 = .Left_Bracket, 428 | win32.VK_OEM_5 = .Backslash, 429 | win32.VK_OEM_6 = .Right_Bracket, 430 | win32.VK_OEM_3 = .Grave_Accent, 431 | 432 | win32.VK_SPACE = .Space, 433 | win32.VK_ESCAPE = .Escape, 434 | win32.VK_RETURN = .Enter, 435 | win32.VK_TAB = .Tab, 436 | win32.VK_BACK = .Backspace, 437 | win32.VK_INSERT = .Insert, 438 | win32.VK_DELETE = .Delete, 439 | win32.VK_RIGHT = .Right, 440 | win32.VK_LEFT = .Left, 441 | win32.VK_DOWN = .Down, 442 | win32.VK_UP = .Up, 443 | win32.VK_PRIOR = .Page_Up, 444 | win32.VK_NEXT = .Page_Down, 445 | win32.VK_HOME = .Home, 446 | win32.VK_END = .End, 447 | win32.VK_CAPITAL = .Caps_Lock, 448 | win32.VK_SCROLL = .Scroll_Lock, 449 | win32.VK_NUMLOCK = .Num_Lock, 450 | win32.VK_PRINT = .Print_Screen, 451 | win32.VK_PAUSE = .Pause, 452 | 453 | win32.VK_F1 = .F1, 454 | win32.VK_F2 = .F2, 455 | win32.VK_F3 = .F3, 456 | win32.VK_F4 = .F4, 457 | win32.VK_F5 = .F5, 458 | win32.VK_F6 = .F6, 459 | win32.VK_F7 = .F7, 460 | win32.VK_F8 = .F8, 461 | win32.VK_F9 = .F9, 462 | win32.VK_F10 = .F10, 463 | win32.VK_F11 = .F11, 464 | win32.VK_F12 = .F12, 465 | 466 | win32.VK_LSHIFT = .Left_Shift, 467 | win32.VK_LCONTROL = .Left_Control, 468 | win32.VK_LMENU = .Left_Alt, 469 | win32.VK_MENU = .Left_Alt, 470 | win32.VK_LWIN = .Left_Super, 471 | win32.VK_RSHIFT = .Right_Shift, 472 | win32.VK_RCONTROL = .Right_Control, 473 | win32.VK_RMENU = .Right_Alt, 474 | win32.VK_RWIN = .Right_Super, 475 | win32.VK_APPS = .Menu, 476 | 477 | win32.VK_NUMPAD0 = .NP_0, 478 | win32.VK_NUMPAD1 = .NP_1, 479 | win32.VK_NUMPAD2 = .NP_2, 480 | win32.VK_NUMPAD3 = .NP_3, 481 | win32.VK_NUMPAD4 = .NP_4, 482 | win32.VK_NUMPAD5 = .NP_5, 483 | win32.VK_NUMPAD6 = .NP_6, 484 | win32.VK_NUMPAD7 = .NP_7, 485 | win32.VK_NUMPAD8 = .NP_8, 486 | win32.VK_NUMPAD9 = .NP_9, 487 | 488 | win32.VK_DECIMAL = .NP_Decimal, 489 | win32.VK_DIVIDE = .NP_Divide, 490 | win32.VK_MULTIPLY = .NP_Multiply, 491 | win32.VK_SUBTRACT = .NP_Subtract, 492 | win32.VK_ADD = .NP_Add, 493 | 494 | // NP_Enter is handled separately 495 | 496 | win32.VK_OEM_NEC_EQUAL = .NP_Equal, 497 | } -------------------------------------------------------------------------------- /karl2d.doc.odin: -------------------------------------------------------------------------------- 1 | // This file is purely documentational. It is generated from the contents of 'karl2d.odin'. 2 | #+build ignore 3 | package karl2d 4 | 5 | //-----------------------------------------------// 6 | // SETUP, WINDOW MANAGEMENT AND FRAME MANAGEMENT // 7 | //-----------------------------------------------// 8 | 9 | // Opens a window and initializes some internal state. The internal state will use `allocator` for 10 | // all dynamically allocated memory. The return value can be ignored unless you need to later call 11 | // `set_internal_state`. 12 | init :: proc(window_width: int, window_height: int, window_title: string, 13 | window_creation_flags := Window_Flags {}, 14 | allocator := context.allocator, loc := #caller_location) -> ^State 15 | 16 | // Returns true if the program wants to shut down. This happens when for example pressing the close 17 | // button on the window. The application can decide if it wants to shut down or if it wants to show 18 | // some kind of confirmation dialogue and shut down later. 19 | // 20 | // Commonly used for creating the "main loop" of a game. 21 | shutdown_wanted :: proc() -> bool 22 | 23 | // Closes the window and cleans up the internal state. 24 | shutdown :: proc() 25 | 26 | // Clear the backbuffer with supplied color. 27 | clear :: proc(color: Color) 28 | 29 | // Present the backbuffer. Call at end of frame to make everything you've drawn appear on the 30 | // screen. Also clears the frame_allocator that Karl2D uses for allocations that have the lifetime 31 | // of a single frame. 32 | present :: proc() 33 | 34 | // Call at start or end of frame to process all events that have arrived to the window. This 35 | // includes keyboard, mouse, gamepad and window events. 36 | // 37 | // WARNING: Not calling this will make your program impossible to interact with. 38 | process_events :: proc() 39 | 40 | get_screen_width :: proc() -> int 41 | 42 | get_screen_height :: proc() -> int 43 | 44 | set_window_position :: proc(x: int, y: int) 45 | 46 | set_window_size :: proc(width: int, height: int) 47 | 48 | // Fetch the scale of the window. This usually comes from some DPI scaling setting in the OS. 49 | // 1 means 100% scale, 1.5 means 150% etc. 50 | get_window_scale :: proc() -> f32 51 | 52 | set_window_flags :: proc(flags: Window_Flags) 53 | 54 | // Flushes the current batch. This sends off everything to the GPU that has been queued in the 55 | // current batch. Normally, you do not need to do this manually. It is done automatically when these 56 | // procedures run: 57 | // 58 | // - present 59 | // - set_camera 60 | // - set_shader 61 | // - set_shader_constant 62 | // - set_scissor_rect 63 | // - draw_texture_* IF previous draw did not use the same texture (1) 64 | // - draw_rect_*, draw_circle_*, draw_line IF previous draw did not use the shapes drawing texture (2) 65 | // 66 | // (1) When drawing textures, the current texture is fed into the active shader. Everything within 67 | // the same batch must use the same texture. So drawing with a new texture will draw the current 68 | // batch. You can combine several textures into an atlas to get bigger batches. 69 | // 70 | // (2) In order to use the same shader for shapes drawing and textured drawing, the shapes drawing 71 | // uses a blank, white texture. For the same reasons as (1), drawing something else than shapes 72 | // before drawing a shape will break up the batches. TODO: Add possibility to customize shape 73 | // drawing texture so that you can put it into an atlas. 74 | // 75 | // The batch has maximum size of VERTEX_BUFFER_MAX bytes. The shader dictates how big a vertex is 76 | // so the maximum number of vertices that can be drawn in each batch is 77 | // VERTEX_BUFFER_MAX / shader.vertex_size 78 | draw_current_batch :: proc() 79 | 80 | //-------// 81 | // INPUT // 82 | //-------// 83 | 84 | // Returns true if a keyboard key went down between the current and the previous frame. Set when 85 | // 'process_events' runs (probably once per frame). 86 | key_went_down :: proc(key: Keyboard_Key) -> bool 87 | 88 | // Returns true if a keyboard key went up (was released) between the current and the previous frame. 89 | // Set when 'process_events' runs (probably once per frame). 90 | key_went_up :: proc(key: Keyboard_Key) -> bool 91 | 92 | // Returns true if a keyboard is currently being held down. Set when 'process_events' runs (probably 93 | // once per frame). 94 | key_is_held :: proc(key: Keyboard_Key) -> bool 95 | 96 | mouse_button_went_down :: proc(button: Mouse_Button) -> bool 97 | 98 | mouse_button_went_up :: proc(button: Mouse_Button) -> bool 99 | 100 | mouse_button_is_held :: proc(button: Mouse_Button) -> bool 101 | 102 | get_mouse_wheel_delta :: proc() -> f32 103 | 104 | get_mouse_position :: proc() -> Vec2 105 | 106 | get_mouse_delta :: proc() -> Vec2 107 | 108 | is_gamepad_active :: proc(gamepad: Gamepad_Index) -> bool 109 | 110 | gamepad_button_went_down :: proc(gamepad: Gamepad_Index, button: Gamepad_Button) -> bool 111 | 112 | gamepad_button_went_up :: proc(gamepad: Gamepad_Index, button: Gamepad_Button) -> bool 113 | 114 | gamepad_button_is_held :: proc(gamepad: Gamepad_Index, button: Gamepad_Button) -> bool 115 | 116 | get_gamepad_axis :: proc(gamepad: Gamepad_Index, axis: Gamepad_Axis) -> f32 117 | 118 | // Set the left and right vibration motor speed. The range of left and right is 0 to 1. Note that on 119 | // most gamepads, the left motor is "low frequency" and the right motor is "high frequency". They do 120 | // not vibrate with the same speed. 121 | set_gamepad_vibration :: proc(gamepad: Gamepad_Index, left: f32, right: f32) 122 | 123 | //---------// 124 | // DRAWING // 125 | //---------// 126 | draw_rect :: proc(r: Rect, c: Color) 127 | 128 | draw_rect_vec :: proc(pos: Vec2, size: Vec2, c: Color) 129 | 130 | draw_rect_ex :: proc(r: Rect, origin: Vec2, rot: f32, c: Color) 131 | 132 | draw_rect_outline :: proc(r: Rect, thickness: f32, color: Color) 133 | 134 | draw_circle :: proc(center: Vec2, radius: f32, color: Color, segments := 16) 135 | 136 | draw_circle_outline :: proc(center: Vec2, radius: f32, thickness: f32, color: Color, segments := 16) 137 | 138 | draw_line :: proc(start: Vec2, end: Vec2, thickness: f32, color: Color) 139 | 140 | draw_texture :: proc(tex: Texture, pos: Vec2, tint := WHITE) 141 | 142 | draw_texture_rect :: proc(tex: Texture, rect: Rect, pos: Vec2, tint := WHITE) 143 | 144 | draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rotation: f32, tint := WHITE) 145 | 146 | measure_text :: proc(text: string, font_size: f32) -> Vec2 147 | 148 | draw_text :: proc(text: string, pos: Vec2, font_size: f32, color: Color) 149 | 150 | draw_text_ex :: proc(font: Font_Handle, text: string, pos: Vec2, font_size: f32, color: Color) 151 | 152 | //--------------------// 153 | // TEXTURE MANAGEMENT // 154 | //--------------------// 155 | create_texture :: proc(width: int, height: int, format: Pixel_Format) -> Texture 156 | 157 | // Load a texture from disk and upload it to the GPU so you can draw it to the screen. 158 | // Supports PNG, BMP, TGA and baseline PNG. Note that progressive PNG files are not supported! 159 | // 160 | // The `options` parameter can be used to specify things things such as premultiplication of alpha. 161 | load_texture_from_file :: proc(filename: string, options: Load_Texture_Options = {}) -> Texture 162 | 163 | // TODO should we have an error here or rely on check the handle of the texture? 164 | load_texture_from_bytes :: proc(bytes: []u8, width: int, height: int, format: Pixel_Format) -> Texture 165 | 166 | // Get a rectangle that spans the whole texture. Coordinates will be (x, y) = (0, 0) and size 167 | // (w, h) = (texture_width, texture_height) 168 | get_texture_rect :: proc(t: Texture) -> Rect 169 | 170 | // Update a texture with new pixels. `bytes` is the new pixel data. `rect` is the rectangle in 171 | // `tex` where the new pixels should end up. 172 | update_texture :: proc(tex: Texture, bytes: []u8, rect: Rect) -> bool 173 | 174 | destroy_texture :: proc(tex: Texture) 175 | 176 | // Controls how a texture should be filtered. You can choose "point" or "linear" filtering. Which 177 | // means "pixly" or "smooth". This filter will be used for up and down-scaling as well as for 178 | // mipmap sampling. Use `set_texture_filter_ex` if you need to control these settings separately. 179 | set_texture_filter :: proc(t: Texture, filter: Texture_Filter) 180 | 181 | // Controls how a texture should be filtered. `scale_down_filter` and `scale_up_filter` controls how 182 | // the texture is filtered when we render the texture at a smaller or larger size. 183 | // `mip_filter` controls how the texture is filtered when it is sampled using _mipmapping_. 184 | // 185 | // TODO: Add mipmapping generation controls for texture and refer to it from here. 186 | set_texture_filter_ex :: proc( 187 | t: Texture, 188 | scale_down_filter: Texture_Filter, 189 | scale_up_filter: Texture_Filter, 190 | mip_filter: Texture_Filter, 191 | ) 192 | 193 | //-----------------// 194 | // RENDER TEXTURES // 195 | //-----------------// 196 | 197 | // Create a texture that you can render into. Meaning that you can draw into it instead of drawing 198 | // onto the screen. Set the texture using `set_render_texture`. 199 | create_render_texture :: proc(width: int, height: int) -> Render_Texture 200 | 201 | // Destroy a Render_Texture previously created using `create_render_texture`. 202 | destroy_render_texture :: proc(render_texture: Render_Texture) 203 | 204 | // Make all rendering go into a texture instead of onto the screen. Create the render texture using 205 | // `create_render_texture`. Pass `nil` to resume drawing onto the screen. 206 | set_render_texture :: proc(render_texture: Maybe(Render_Texture)) 207 | 208 | //-------// 209 | // FONTS // 210 | //-------// 211 | load_font_from_file :: proc(filename: string) -> Font_Handle 212 | 213 | load_font_from_bytes :: proc(data: []u8) -> Font_Handle 214 | 215 | destroy_font :: proc(font: Font_Handle) 216 | 217 | get_default_font :: proc() -> Font_Handle 218 | 219 | //---------// 220 | // SHADERS // 221 | //---------// 222 | load_shader_from_file :: proc( 223 | vertex_filename: string, 224 | fragment_filename: string, 225 | layout_formats: []Pixel_Format = {} 226 | ) -> Shader 227 | 228 | load_shader_from_memory :: proc( 229 | vertex_shader_bytes: []byte, 230 | fragment_shader_bytes: []byte, 231 | layout_formats: []Pixel_Format = {}, 232 | ) -> Shader 233 | 234 | destroy_shader :: proc(shader: Shader) 235 | 236 | get_default_shader :: proc() -> Shader 237 | 238 | set_shader :: proc(shader: Maybe(Shader)) 239 | 240 | set_shader_constant :: proc(shd: Shader, loc: Shader_Constant_Location, val: any) 241 | 242 | override_shader_input :: proc(shader: Shader, input: int, val: any) 243 | 244 | pixel_format_size :: proc(f: Pixel_Format) -> int 245 | 246 | //-------------------------------// 247 | // CAMERA AND COORDINATE SYSTEMS // 248 | //-------------------------------// 249 | set_camera :: proc(camera: Maybe(Camera)) 250 | 251 | screen_to_world :: proc(pos: Vec2, camera: Camera) -> Vec2 252 | 253 | world_to_screen :: proc(pos: Vec2, camera: Camera) -> Vec2 254 | 255 | get_camera_view_matrix :: proc(c: Camera) -> Mat4 256 | 257 | get_camera_world_matrix :: proc(c: Camera) -> Mat4 258 | 259 | //------// 260 | // MISC // 261 | //------// 262 | 263 | // Choose how the alpha channel is used when mixing half-transparent color with what is already 264 | // drawn. The default is the .Alpha mode, but you also have the option of using .Premultiply_Alpha. 265 | set_blend_mode :: proc(mode: Blend_Mode) 266 | 267 | set_scissor_rect :: proc(scissor_rect: Maybe(Rect)) 268 | 269 | // Restore the internal state using the pointer returned by `init`. Useful after reloading the 270 | // library (for example, when doing code hot reload). 271 | set_internal_state :: proc(state: ^State) 272 | 273 | //---------------------// 274 | // TYPES AND CONSTANTS // 275 | //---------------------// 276 | Vec2 :: [2]f32 277 | 278 | Vec3 :: [3]f32 279 | 280 | Vec4 :: [4]f32 281 | 282 | Mat4 :: matrix[4,4]f32 283 | 284 | // A two dimensional vector of integer numeric type. 285 | Vec2i :: [2]int 286 | 287 | // A rectangle that sits at position (x, y) and has size (w, h). 288 | Rect :: struct { 289 | x, y: f32, 290 | w, h: f32, 291 | } 292 | 293 | // An RGBA (Red, Green, Blue, Alpha) color. Each channel can have a value between 0 and 255. 294 | Color :: [4]u8 295 | 296 | WHITE :: Color { 255, 255, 255, 255 } 297 | BLACK :: Color { 0, 0, 0, 255 } 298 | GRAY :: Color { 127, 127, 127, 255 } 299 | RED :: Color { 198, 40, 90, 255 } 300 | GREEN :: Color { 30, 240, 30, 255 } 301 | BLANK :: Color { 0, 0, 0, 0 } 302 | BLUE :: Color { 30, 116, 240, 255 } 303 | 304 | // These are from Raylib. They are here so you can easily port a Raylib program to Karl2D. 305 | RL_LIGHTGRAY :: Color { 200, 200, 200, 255 } 306 | RL_GRAY :: Color { 130, 130, 130, 255 } 307 | RL_DARKGRAY :: Color { 80, 80, 80, 255 } 308 | RL_YELLOW :: Color { 253, 249, 0, 255 } 309 | RL_GOLD :: Color { 255, 203, 0, 255 } 310 | RL_ORANGE :: Color { 255, 161, 0, 255 } 311 | RL_PINK :: Color { 255, 109, 194, 255 } 312 | RL_RED :: Color { 230, 41, 55, 255 } 313 | RL_MAROON :: Color { 190, 33, 55, 255 } 314 | RL_GREEN :: Color { 0, 228, 48, 255 } 315 | RL_LIME :: Color { 0, 158, 47, 255 } 316 | RL_DARKGREEN :: Color { 0, 117, 44, 255 } 317 | RL_SKYBLUE :: Color { 102, 191, 255, 255 } 318 | RL_BLUE :: Color { 0, 121, 241, 255 } 319 | RL_DARKBLUE :: Color { 0, 82, 172, 255 } 320 | RL_PURPLE :: Color { 200, 122, 255, 255 } 321 | RL_VIOLET :: Color { 135, 60, 190, 255 } 322 | RL_DARKPURPLE :: Color { 112, 31, 126, 255 } 323 | RL_BEIGE :: Color { 211, 176, 131, 255 } 324 | RL_BROWN :: Color { 127, 106, 79, 255 } 325 | RL_DARKBROWN :: Color { 76, 63, 47, 255 } 326 | RL_WHITE :: WHITE 327 | RL_BLACK :: BLACK 328 | RL_BLANK :: BLANK 329 | RL_MAGENTA :: Color { 255, 0, 255, 255 } 330 | RL_RAYWHITE :: Color { 245, 245, 245, 255 } 331 | 332 | Texture :: struct { 333 | handle: Texture_Handle, 334 | width: int, 335 | height: int, 336 | } 337 | 338 | Load_Texture_Option :: enum { 339 | Premultiply_Alpha, 340 | } 341 | 342 | Load_Texture_Options :: bit_set[Load_Texture_Option] 343 | 344 | Blend_Mode :: enum { 345 | Alpha, 346 | Premultiplied_Alpha, // Requires the alpha-channel to be multiplied into texture RGB channels. 347 | } 348 | 349 | Render_Texture :: struct { 350 | texture: Texture, 351 | render_target: Render_Target_Handle, 352 | } 353 | 354 | Texture_Filter :: enum { 355 | Point, // Similar to "nearest neighbor". Pixly texture scaling. 356 | Linear, // Smoothed texture scaling. 357 | } 358 | 359 | Camera :: struct { 360 | target: Vec2, 361 | offset: Vec2, 362 | rotation: f32, 363 | zoom: f32, 364 | } 365 | 366 | Window_Flag :: enum { 367 | Resizable, 368 | } 369 | 370 | Window_Flags :: bit_set[Window_Flag] 371 | 372 | Shader_Handle :: distinct Handle 373 | 374 | SHADER_NONE :: Shader_Handle {} 375 | 376 | Shader_Constant_Location :: struct { 377 | offset: int, 378 | size: int, 379 | } 380 | 381 | Shader :: struct { 382 | handle: Shader_Handle, 383 | 384 | // We store the CPU-side value of all constants in a single buffer to have less allocations. 385 | // The 'constants' array says where in this buffer each constant is, and 'constant_lookup' 386 | // maps a name to a constant location. 387 | constants_data: []u8, 388 | constants: []Shader_Constant_Location, 389 | constant_lookup: map[string]Shader_Constant_Location, 390 | 391 | // Maps built in constant types such as "model view projection matrix" to a location. 392 | constant_builtin_locations: [Shader_Builtin_Constant]Maybe(Shader_Constant_Location), 393 | 394 | texture_bindpoints: []Texture_Handle, 395 | texture_lookup: map[string]int, 396 | default_texture_index: Maybe(int), 397 | 398 | inputs: []Shader_Input, 399 | input_overrides: []Shader_Input_Value_Override, 400 | default_input_offsets: [Shader_Default_Inputs]int, 401 | vertex_size: int, 402 | } 403 | 404 | SHADER_INPUT_VALUE_MAX_SIZE :: 256 405 | 406 | Shader_Input_Value_Override :: struct { 407 | val: [SHADER_INPUT_VALUE_MAX_SIZE]u8, 408 | used: int, 409 | } 410 | 411 | Shader_Input_Type :: enum { 412 | F32, 413 | Vec2, 414 | Vec3, 415 | Vec4, 416 | } 417 | 418 | Shader_Builtin_Constant :: enum { 419 | MVP, 420 | } 421 | 422 | Shader_Default_Inputs :: enum { 423 | Unknown, 424 | Position, 425 | UV, 426 | Color, 427 | } 428 | 429 | Shader_Input :: struct { 430 | name: string, 431 | register: int, 432 | type: Shader_Input_Type, 433 | format: Pixel_Format, 434 | } 435 | 436 | Pixel_Format :: enum { 437 | Unknown, 438 | 439 | RGBA_32_Float, 440 | RGB_32_Float, 441 | RG_32_Float, 442 | R_32_Float, 443 | 444 | RGBA_8_Norm, 445 | RG_8_Norm, 446 | R_8_Norm, 447 | 448 | R_8_UInt, 449 | } 450 | 451 | Font :: struct { 452 | atlas: Texture, 453 | 454 | // internal 455 | fontstash_handle: int, 456 | } 457 | 458 | Handle :: hm.Handle 459 | Texture_Handle :: distinct Handle 460 | Render_Target_Handle :: distinct Handle 461 | Font_Handle :: distinct int 462 | 463 | FONT_NONE :: Font_Handle {} 464 | TEXTURE_NONE :: Texture_Handle {} 465 | RENDER_TARGET_NONE :: Render_Target_Handle {} 466 | 467 | // This keeps track of the internal state of the library. Usually, you do not need to poke at it. 468 | // It is created and kept as a global variable when 'init' is called. However, 'init' also returns 469 | // the pointer to it, so you can later use 'set_internal_state' to restore it (after for example hot 470 | // reload). 471 | State :: struct { 472 | allocator: runtime.Allocator, 473 | frame_arena: runtime.Arena, 474 | frame_allocator: runtime.Allocator, 475 | win: Window_Interface, 476 | window_state: rawptr, 477 | rb: Render_Backend_Interface, 478 | rb_state: rawptr, 479 | 480 | fs: fs.FontContext, 481 | 482 | shutdown_wanted: bool, 483 | 484 | mouse_position: Vec2, 485 | mouse_delta: Vec2, 486 | mouse_wheel_delta: f32, 487 | 488 | key_went_down: #sparse [Keyboard_Key]bool, 489 | key_went_up: #sparse [Keyboard_Key]bool, 490 | key_is_held: #sparse [Keyboard_Key]bool, 491 | 492 | mouse_button_went_down: #sparse [Mouse_Button]bool, 493 | mouse_button_went_up: #sparse [Mouse_Button]bool, 494 | mouse_button_is_held: #sparse [Mouse_Button]bool, 495 | 496 | gamepad_button_went_down: [MAX_GAMEPADS]#sparse [Gamepad_Button]bool, 497 | gamepad_button_went_up: [MAX_GAMEPADS]#sparse [Gamepad_Button]bool, 498 | gamepad_button_is_held: [MAX_GAMEPADS]#sparse [Gamepad_Button]bool, 499 | 500 | window: Window_Handle, 501 | 502 | default_font: Font_Handle, 503 | fonts: [dynamic]Font, 504 | shape_drawing_texture: Texture_Handle, 505 | batch_font: Font_Handle, 506 | batch_camera: Maybe(Camera), 507 | batch_shader: Shader, 508 | batch_scissor: Maybe(Rect), 509 | batch_texture: Texture_Handle, 510 | batch_render_target: Render_Target_Handle, 511 | batch_blend_mode: Blend_Mode, 512 | 513 | view_matrix: Mat4, 514 | proj_matrix: Mat4, 515 | 516 | depth: f32, 517 | depth_start: f32, 518 | depth_increment: f32, 519 | vertex_buffer_cpu: []u8, 520 | vertex_buffer_cpu_used: int, 521 | default_shader: Shader, 522 | } 523 | 524 | // Support for up to 255 mouse buttons. Cast an int to type `Mouse_Button` to use things outside the 525 | // options presented here. 526 | Mouse_Button :: enum { 527 | Left, 528 | Right, 529 | Middle, 530 | Max = 255, 531 | } 532 | 533 | // Based on Raylib / GLFW 534 | Keyboard_Key :: enum { 535 | None = 0, 536 | 537 | // Numeric keys (top row) 538 | N0 = 48, 539 | N1 = 49, 540 | N2 = 50, 541 | N3 = 51, 542 | N4 = 52, 543 | N5 = 53, 544 | N6 = 54, 545 | N7 = 55, 546 | N8 = 56, 547 | N9 = 57, 548 | 549 | // Letter keys 550 | A = 65, 551 | B = 66, 552 | C = 67, 553 | D = 68, 554 | E = 69, 555 | F = 70, 556 | G = 71, 557 | H = 72, 558 | I = 73, 559 | J = 74, 560 | K = 75, 561 | L = 76, 562 | M = 77, 563 | N = 78, 564 | O = 79, 565 | P = 80, 566 | Q = 81, 567 | R = 82, 568 | S = 83, 569 | T = 84, 570 | U = 85, 571 | V = 86, 572 | W = 87, 573 | X = 88, 574 | Y = 89, 575 | Z = 90, 576 | 577 | // Special characters 578 | Apostrophe = 39, 579 | Comma = 44, 580 | Minus = 45, 581 | Period = 46, 582 | Slash = 47, 583 | Semicolon = 59, 584 | Equal = 61, 585 | Left_Bracket = 91, 586 | Backslash = 92, 587 | Right_Bracket = 93, 588 | Grave_Accent = 96, 589 | 590 | // Function keys, modifiers, caret control etc 591 | Space = 32, 592 | Escape = 256, 593 | Enter = 257, 594 | Tab = 258, 595 | Backspace = 259, 596 | Insert = 260, 597 | Delete = 261, 598 | Right = 262, 599 | Left = 263, 600 | Down = 264, 601 | Up = 265, 602 | Page_Up = 266, 603 | Page_Down = 267, 604 | Home = 268, 605 | End = 269, 606 | Caps_Lock = 280, 607 | Scroll_Lock = 281, 608 | Num_Lock = 282, 609 | Print_Screen = 283, 610 | Pause = 284, 611 | F1 = 290, 612 | F2 = 291, 613 | F3 = 292, 614 | F4 = 293, 615 | F5 = 294, 616 | F6 = 295, 617 | F7 = 296, 618 | F8 = 297, 619 | F9 = 298, 620 | F10 = 299, 621 | F11 = 300, 622 | F12 = 301, 623 | Left_Shift = 340, 624 | Left_Control = 341, 625 | Left_Alt = 342, 626 | Left_Super = 343, 627 | Right_Shift = 344, 628 | Right_Control = 345, 629 | Right_Alt = 346, 630 | Right_Super = 347, 631 | Menu = 348, 632 | 633 | // Numpad keys 634 | NP_0 = 320, 635 | NP_1 = 321, 636 | NP_2 = 322, 637 | NP_3 = 323, 638 | NP_4 = 324, 639 | NP_5 = 325, 640 | NP_6 = 326, 641 | NP_7 = 327, 642 | NP_8 = 328, 643 | NP_9 = 329, 644 | NP_Decimal = 330, 645 | NP_Divide = 331, 646 | NP_Multiply = 332, 647 | NP_Subtract = 333, 648 | NP_Add = 334, 649 | NP_Enter = 335, 650 | NP_Equal = 336, 651 | } 652 | 653 | MAX_GAMEPADS :: 4 654 | 655 | // A value between 0 and MAX_GAMEPADS - 1 656 | Gamepad_Index :: int 657 | 658 | Gamepad_Axis :: enum { 659 | Left_Stick_X, 660 | Left_Stick_Y, 661 | Right_Stick_X, 662 | Right_Stick_Y, 663 | Left_Trigger, 664 | Right_Trigger, 665 | } 666 | 667 | Gamepad_Button :: enum { 668 | // DPAD buttons 669 | Left_Face_Up, 670 | Left_Face_Down, 671 | Left_Face_Left, 672 | Left_Face_Right, 673 | 674 | Right_Face_Up, // XBOX: Y, PS: Triangle 675 | Right_Face_Down, // XBOX: A, PS: X 676 | Right_Face_Left, // XBOX: X, PS: Square 677 | Right_Face_Right, // XBOX: B, PS: Circle 678 | 679 | Left_Shoulder, 680 | Left_Trigger, 681 | 682 | Right_Shoulder, 683 | Right_Trigger, 684 | 685 | Left_Stick_Press, // Clicking the left analogue stick 686 | Right_Stick_Press, // Clicking the right analogue stick 687 | 688 | Middle_Face_Left, // Select / back / options button 689 | Middle_Face_Middle, // PS button (not available on XBox) 690 | Middle_Face_Right, // Start 691 | } 692 | -------------------------------------------------------------------------------- /render_backend_gl.odin: -------------------------------------------------------------------------------- 1 | #+build windows, darwin, linux 2 | #+private file 3 | 4 | package karl2d 5 | 6 | @(private="package") 7 | RENDER_BACKEND_INTERFACE_GL :: Render_Backend_Interface { 8 | state_size = gl_state_size, 9 | init = gl_init, 10 | shutdown = gl_shutdown, 11 | clear = gl_clear, 12 | present = gl_present, 13 | draw = gl_draw, 14 | resize_swapchain = gl_resize_swapchain, 15 | get_swapchain_width = gl_get_swapchain_width, 16 | get_swapchain_height = gl_get_swapchain_height, 17 | flip_z = gl_flip_z, 18 | set_internal_state = gl_set_internal_state, 19 | create_texture = gl_create_texture, 20 | load_texture = gl_load_texture, 21 | update_texture = gl_update_texture, 22 | destroy_texture = gl_destroy_texture, 23 | create_render_texture = gl_create_render_texture, 24 | destroy_render_target = gl_destroy_render_target, 25 | set_texture_filter = gl_set_texture_filter, 26 | load_shader = gl_load_shader, 27 | destroy_shader = gl_destroy_shader, 28 | 29 | default_shader_vertex_source = gl_default_shader_vertex_source, 30 | default_shader_fragment_source = gl_default_shader_fragment_source, 31 | } 32 | 33 | import "base:runtime" 34 | import gl "vendor:OpenGL" 35 | import hm "handle_map" 36 | import "core:log" 37 | import "core:strings" 38 | import "core:slice" 39 | import la "core:math/linalg" 40 | 41 | _ :: la 42 | 43 | GL_State :: struct { 44 | window_handle: Window_Handle, 45 | width: int, 46 | height: int, 47 | allocator: runtime.Allocator, 48 | shaders: hm.Handle_Map(GL_Shader, Shader_Handle, 1024*10), 49 | ctx: GL_Context, 50 | vertex_buffer_gpu: u32, 51 | textures: hm.Handle_Map(GL_Texture, Texture_Handle, 1024*10), 52 | } 53 | 54 | GL_Shader_Constant_Buffer :: struct { 55 | buffer: u32, 56 | size: int, 57 | block_index: u32, 58 | } 59 | 60 | GL_Shader_Constant_Type :: enum { 61 | Uniform, 62 | Block_Variable, 63 | } 64 | 65 | // OpenGL can have constants both in blocks (like constant buffers in D3D11), or as stand-alone 66 | // uniforms. We support both. 67 | GL_Shader_Constant :: struct { 68 | type: GL_Shader_Constant_Type, 69 | 70 | // if type is Uniform, then this is the uniform loc 71 | // if type is Block_Variable, then this is the block loc 72 | loc: u32, 73 | 74 | // if this is a block variable, then this is the offset to it 75 | block_variable_offset: u32, 76 | 77 | // if type is Uniform, then this contains the GL type of the uniform 78 | uniform_type: u32, 79 | } 80 | 81 | GL_Texture :: struct { 82 | handle: Texture_Handle, 83 | id: u32, 84 | format: Pixel_Format, 85 | } 86 | 87 | GL_Texture_Binding :: struct { 88 | loc: i32, 89 | } 90 | 91 | GL_Shader :: struct { 92 | handle: Shader_Handle, 93 | 94 | // This is like the "input layout" 95 | vao: u32, 96 | 97 | program: u32, 98 | 99 | constant_buffers: []GL_Shader_Constant_Buffer, 100 | constants: []GL_Shader_Constant, 101 | texture_bindings: []GL_Texture_Binding, 102 | } 103 | 104 | s: ^GL_State 105 | 106 | gl_state_size :: proc() -> int { 107 | return size_of(GL_State) 108 | } 109 | 110 | gl_init :: proc(state: rawptr, window_handle: Window_Handle, swapchain_width, swapchain_height: int, allocator := context.allocator) { 111 | s = (^GL_State)(state) 112 | s.window_handle = window_handle 113 | s.width = swapchain_width 114 | s.height = swapchain_height 115 | s.allocator = allocator 116 | 117 | ctx, ctx_ok := _gl_get_context(window_handle) 118 | 119 | if !ctx_ok { 120 | log.panic("Could not find a valid pixel format for gl context") 121 | } 122 | 123 | s.ctx = ctx 124 | _gl_load_procs() 125 | gl.Enable(gl.DEPTH_TEST) 126 | gl.DepthFunc(gl.GREATER) 127 | 128 | gl.GenBuffers(1, &s.vertex_buffer_gpu) 129 | gl.BindBuffer(gl.ARRAY_BUFFER, s.vertex_buffer_gpu) 130 | gl.BufferData(gl.ARRAY_BUFFER, VERTEX_BUFFER_MAX, nil, gl.DYNAMIC_DRAW) 131 | 132 | gl.Enable(gl.BLEND) 133 | } 134 | 135 | gl_shutdown :: proc() { 136 | gl.DeleteBuffers(1, &s.vertex_buffer_gpu) 137 | _gl_destroy_context(s.ctx) 138 | } 139 | 140 | gl_clear :: proc(render_texture: Render_Target_Handle, color: Color) { 141 | c := f32_color_from_color(color) 142 | gl.ClearColor(c.r, c.g, c.b, c.a) 143 | gl.ClearDepth(-1) 144 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 145 | } 146 | 147 | gl_present :: proc() { 148 | _gl_present(s.window_handle) 149 | } 150 | 151 | gl_draw :: proc( 152 | shd: Shader, 153 | render_texture: Render_Target_Handle, 154 | bound_textures: []Texture_Handle, 155 | scissor: Maybe(Rect), 156 | blend_mode: Blend_Mode, 157 | vertex_buffer: []u8, 158 | ) { 159 | gl_shd := hm.get(&s.shaders, shd.handle) 160 | 161 | if gl_shd == nil { 162 | return 163 | } 164 | 165 | switch blend_mode { 166 | case .Alpha: gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 167 | case .Premultiplied_Alpha: gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 168 | } 169 | 170 | gl.EnableVertexAttribArray(0) 171 | gl.EnableVertexAttribArray(1) 172 | gl.EnableVertexAttribArray(2) 173 | 174 | gl.UseProgram(gl_shd.program) 175 | assert(len(shd.constants) == len(gl_shd.constants)) 176 | 177 | cpu_data := shd.constants_data 178 | for cidx in 0.. int { 325 | return s.width 326 | } 327 | 328 | gl_get_swapchain_height :: proc() -> int { 329 | return s.height 330 | } 331 | 332 | gl_flip_z :: proc() -> bool { 333 | return false 334 | } 335 | 336 | gl_set_internal_state :: proc(state: rawptr) { 337 | s = (^GL_State)(state) 338 | } 339 | 340 | create_texture :: proc(width: int, height: int, format: Pixel_Format, data: rawptr) -> Texture_Handle { 341 | id: u32 342 | gl.GenTextures(1, &id) 343 | gl.BindTexture(gl.TEXTURE_2D, id) 344 | 345 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) 346 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) 347 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) 348 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) 349 | 350 | pf := gl_translate_pixel_format(format) 351 | gl.TexImage2D(gl.TEXTURE_2D, 0, pf, i32(width), i32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, data) 352 | 353 | tex := GL_Texture { 354 | id = id, 355 | format = format, 356 | } 357 | 358 | return hm.add(&s.textures, tex) 359 | } 360 | 361 | gl_create_texture :: proc(width: int, height: int, format: Pixel_Format) -> Texture_Handle { 362 | return create_texture(width, height, format, nil) 363 | } 364 | 365 | gl_load_texture :: proc(data: []u8, width: int, height: int, format: Pixel_Format) -> Texture_Handle { 366 | return create_texture(width, height, format, raw_data(data)) 367 | } 368 | 369 | gl_update_texture :: proc(th: Texture_Handle, data: []u8, rect: Rect) -> bool { 370 | tex := hm.get(&s.textures, th) 371 | 372 | if tex == nil { 373 | return false 374 | } 375 | 376 | gl.BindTexture(gl.TEXTURE_2D, tex.id) 377 | gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(rect.x), i32(rect.y), i32(rect.w), i32(rect.h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) 378 | return true 379 | } 380 | 381 | gl_destroy_texture :: proc(th: Texture_Handle) { 382 | tex := hm.get(&s.textures, th) 383 | 384 | if tex == nil { 385 | return 386 | } 387 | 388 | gl.DeleteTextures(1, &tex.id) 389 | hm.remove(&s.textures, th) 390 | } 391 | 392 | gl_create_render_texture :: proc(width: int, height: int) -> (Texture_Handle, Render_Target_Handle) { 393 | return {}, {} 394 | } 395 | 396 | gl_destroy_render_target :: proc(render_target: Render_Target_Handle) { 397 | 398 | } 399 | 400 | gl_set_texture_filter :: proc( 401 | th: Texture_Handle, 402 | scale_down_filter: Texture_Filter, 403 | scale_up_filter: Texture_Filter, 404 | mip_filter: Texture_Filter, 405 | ) { 406 | t := hm.get(&s.textures, th) 407 | 408 | if t == nil { 409 | log.error("Trying to set texture filter for invalid texture %v", th) 410 | return 411 | } 412 | 413 | gl.BindTexture(gl.TEXTURE_2D, t.id) 414 | 415 | min_filter: i32 = scale_down_filter == .Point ? gl.NEAREST : gl.LINEAR 416 | mag_filter: i32 = scale_up_filter == .Point ? gl.NEAREST : gl.LINEAR 417 | 418 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min_filter) 419 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag_filter) 420 | } 421 | 422 | Shader_Compile_Result_OK :: struct {} 423 | 424 | Shader_Compile_Result_Error :: string 425 | 426 | Shader_Compile_Result :: union #no_nil { 427 | Shader_Compile_Result_OK, 428 | Shader_Compile_Result_Error, 429 | } 430 | 431 | compile_shader_from_source :: proc(shader_data: []byte, shader_type: gl.Shader_Type, err_buf: []u8, err_msg: ^string) -> (shader_id: u32, ok: bool) { 432 | shader_id = gl.CreateShader(u32(shader_type)) 433 | length := i32(len(shader_data)) 434 | shader_cstr := cstring(raw_data(shader_data)) 435 | gl.ShaderSource(shader_id, 1, &shader_cstr, &length) 436 | gl.CompileShader(shader_id) 437 | 438 | result: i32 439 | gl.GetShaderiv(shader_id, gl.COMPILE_STATUS, &result) 440 | 441 | if result != 1 { 442 | info_len: i32 443 | gl.GetShaderInfoLog(shader_id, i32(len(err_buf)), &info_len, raw_data(err_buf)) 444 | err_msg^ = string(err_buf[:info_len]) 445 | gl.DeleteShader(shader_id) 446 | return 0, false 447 | } 448 | 449 | return shader_id, true 450 | } 451 | 452 | link_shader :: proc(vs_shader: u32, fs_shader: u32, err_buf: []u8, err_msg: ^string) -> (program_id: u32, ok: bool) { 453 | program_id = gl.CreateProgram() 454 | gl.AttachShader(program_id, vs_shader) 455 | gl.AttachShader(program_id, fs_shader) 456 | gl.LinkProgram(program_id) 457 | 458 | result: i32 459 | gl.GetProgramiv(program_id, gl.LINK_STATUS, &result) 460 | 461 | if result != 1 { 462 | info_len: i32 463 | gl.GetProgramInfoLog(program_id, i32(len(err_buf)), &info_len, raw_data(err_buf)) 464 | err_msg^ = string(err_buf[:info_len]) 465 | gl.DeleteProgram(program_id) 466 | return 0, false 467 | } 468 | 469 | return program_id, true 470 | } 471 | 472 | gl_load_shader :: proc(vs_source: []byte, fs_source: []byte, desc_allocator := frame_allocator, layout_formats: []Pixel_Format = {}) -> (handle: Shader_Handle, desc: Shader_Desc) { 473 | @static err: [1024]u8 474 | err_msg: string 475 | vs_shader, vs_shader_ok := compile_shader_from_source(vs_source, gl.Shader_Type.VERTEX_SHADER, err[:], &err_msg) 476 | 477 | if !vs_shader_ok { 478 | log.error(err_msg) 479 | return {}, {} 480 | } 481 | 482 | fs_shader, fs_shader_ok := compile_shader_from_source(fs_source, gl.Shader_Type.FRAGMENT_SHADER, err[:], &err_msg) 483 | 484 | if !fs_shader_ok { 485 | log.error(err_msg) 486 | return {}, {} 487 | } 488 | 489 | program, program_ok := link_shader(vs_shader, fs_shader, err[:], &err_msg) 490 | 491 | if !program_ok { 492 | log.error(err_msg) 493 | return {}, {} 494 | } 495 | 496 | stride: int 497 | 498 | { 499 | num_attribs: i32 500 | gl.GetProgramiv(program, gl.ACTIVE_ATTRIBUTES, &num_attribs) 501 | desc.inputs = make([]Shader_Input, num_attribs, desc_allocator) 502 | 503 | attrib_name_buf: [256]u8 504 | 505 | for i in 0.. 0 ? layout_formats[loc] : get_shader_input_format(name, type) 540 | desc.inputs[i] = { 541 | name = name, 542 | register = int(loc), 543 | format = format, 544 | type = type, 545 | } 546 | 547 | input_format := get_shader_input_format(name, type) 548 | format_size := pixel_format_size(input_format) 549 | 550 | stride += format_size 551 | } 552 | } 553 | 554 | gl_shd := GL_Shader { 555 | program = program, 556 | } 557 | 558 | gl.GenVertexArrays(1, &gl_shd.vao) 559 | gl.BindVertexArray(gl_shd.vao) 560 | gl.BindBuffer(gl.ARRAY_BUFFER, s.vertex_buffer_gpu) 561 | 562 | offset: int 563 | for idx in 0..= num_active_uniform_blocks { 656 | continue 657 | } 658 | 659 | size: i32 660 | 661 | // TODO investigate if we need std140 layout in the shader or what is fine? 662 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_DATA_SIZE, &size) 663 | 664 | if size == 0 { 665 | log.errorf("Uniform block %v has size 0", name_cstr) 666 | continue 667 | } 668 | 669 | buf: u32 670 | 671 | gl.GenBuffers(1, &buf) 672 | gl.BindBuffer(gl.UNIFORM_BUFFER, buf) 673 | gl.BufferData(gl.UNIFORM_BUFFER, int(size), nil, gl.DYNAMIC_DRAW) 674 | gl.BindBufferBase(gl.UNIFORM_BUFFER, idx, buf) 675 | 676 | gl_shd.constant_buffers[cb_idx] = { 677 | block_index = idx, 678 | buffer = buf, 679 | size = int(size), 680 | } 681 | 682 | num_uniforms: i32 683 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num_uniforms) 684 | 685 | uniform_indices := make([]i32, num_uniforms, frame_allocator) 686 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, raw_data(uniform_indices)) 687 | 688 | for var_idx in 0.. int { 727 | sz: int 728 | switch t { 729 | case gl.FLOAT: sz = 4*1 730 | 731 | case gl.FLOAT_VEC2: sz = 4*2*1 732 | case gl.FLOAT_MAT2: sz = 4*2*2 733 | case gl.FLOAT_MAT2x3: sz = 4*2*3 734 | case gl.FLOAT_MAT2x4: sz = 4*2*4 735 | 736 | case gl.FLOAT_VEC3: sz = 4*3*1 737 | case gl.FLOAT_MAT3x2: sz = 4*3*2 738 | case gl.FLOAT_MAT3: sz = 4*3*3 739 | case gl.FLOAT_MAT3x4: sz = 4*3*4 740 | 741 | case gl.FLOAT_VEC4: sz = 4*4*1 742 | case gl.FLOAT_MAT4x2: sz = 4*4*2 743 | case gl.FLOAT_MAT4x3: sz = 4*4*3 744 | case gl.FLOAT_MAT4: sz = 4*4*4 745 | 746 | case gl.DOUBLE: sz = 8*1 747 | 748 | case gl.DOUBLE_VEC2: sz = 8*2*1 749 | case gl.DOUBLE_MAT2: sz = 8*2*2 750 | case gl.DOUBLE_MAT2x3: sz = 8*2*3 751 | case gl.DOUBLE_MAT2x4: sz = 8*2*4 752 | 753 | case gl.DOUBLE_VEC3: sz = 8*3*1 754 | case gl.DOUBLE_MAT3x2: sz = 8*3*2 755 | case gl.DOUBLE_MAT3: sz = 8*3*3 756 | case gl.DOUBLE_MAT3x4: sz = 8*3*4 757 | 758 | case gl.DOUBLE_VEC4: sz = 8*4*1 759 | case gl.DOUBLE_MAT4x2: sz = 8*4*2 760 | case gl.DOUBLE_MAT4x3: sz = 8*4*3 761 | case gl.DOUBLE_MAT4: sz = 8*4*4 762 | 763 | case gl.BOOL: sz = 4*1 764 | case gl.BOOL_VEC2: sz = 4*2 765 | case gl.BOOL_VEC3: sz = 4*3 766 | case gl.BOOL_VEC4: sz = 4*4 767 | 768 | case gl.INT: sz = 4*1 769 | case gl.INT_VEC2: sz = 4*2 770 | case gl.INT_VEC3: sz = 4*3 771 | case gl.INT_VEC4: sz = 4*4 772 | 773 | case gl.UNSIGNED_INT: sz = 4*1 774 | case gl.UNSIGNED_INT_VEC2: sz = 4*2 775 | case gl.UNSIGNED_INT_VEC3: sz = 4*3 776 | case gl.UNSIGNED_INT_VEC4: sz = 4*4 777 | case: log.errorf("Unhandled uniform type: %x", t) 778 | } 779 | 780 | return sz 781 | } 782 | 783 | gl_translate_pixel_format :: proc(f: Pixel_Format) -> i32 { 784 | switch f { 785 | case .RGBA_32_Float: return gl.RGBA 786 | case .RGB_32_Float: return gl.RGB 787 | case .RG_32_Float: return gl.RG 788 | case .R_32_Float: return gl.R 789 | 790 | case .RGBA_8_Norm: return gl.RGBA8_SNORM 791 | case .RG_8_Norm: return gl.RG8_SNORM 792 | case .R_8_Norm: return gl.R8_SNORM 793 | case .R_8_UInt: return gl.R8_SNORM 794 | 795 | case .Unknown: fallthrough 796 | case: log.error("Unhandled pixel format %v", f) 797 | } 798 | 799 | return 0 800 | } 801 | 802 | 803 | gl_describe_pixel_format :: proc(f: Pixel_Format) -> (format: u32, num_components: i32, normalized: bool) { 804 | switch f { 805 | case .RGBA_32_Float: return gl.FLOAT, 4, false 806 | case .RGB_32_Float: return gl.FLOAT, 3, false 807 | case .RG_32_Float: return gl.FLOAT, 2, false 808 | case .R_32_Float: return gl.FLOAT, 1, false 809 | 810 | case .RGBA_8_Norm: return gl.UNSIGNED_BYTE, 4, true 811 | case .RG_8_Norm: return gl.UNSIGNED_BYTE, 2, true 812 | case .R_8_Norm: return gl.UNSIGNED_BYTE, 1, true 813 | case .R_8_UInt: return gl.BYTE, 1, false 814 | 815 | case .Unknown: 816 | } 817 | 818 | log.errorf("Unknown format %x", format) 819 | return 0, 0, false 820 | } 821 | 822 | gl_destroy_shader :: proc(h: Shader_Handle) { 823 | shd := hm.get(&s.shaders, h) 824 | 825 | if shd == nil { 826 | log.errorf("Invalid shader: %v", h) 827 | return 828 | } 829 | 830 | delete(shd.constant_buffers, s.allocator) 831 | delete(shd.constants, s.allocator) 832 | delete(shd.texture_bindings, s.allocator) 833 | } 834 | 835 | gl_default_shader_vertex_source :: proc() -> []byte { 836 | vertex_source := #load("render_backend_gl_default_vertex_shader.glsl") 837 | return vertex_source 838 | } 839 | 840 | gl_default_shader_fragment_source :: proc() -> []byte { 841 | fragment_source := #load("render_backend_gl_default_fragment_shader.glsl") 842 | return fragment_source 843 | } 844 | 845 | -------------------------------------------------------------------------------- /render_backend_webgl.odin: -------------------------------------------------------------------------------- 1 | #+build js 2 | #+private file 3 | 4 | package karl2d 5 | 6 | @(private="package") 7 | RENDER_BACKEND_INTERFACE_WEBGL :: Render_Backend_Interface { 8 | state_size = webgl_state_size, 9 | init = webgl_init, 10 | shutdown = webgl_shutdown, 11 | clear = webgl_clear, 12 | present = webgl_present, 13 | draw = webgl_draw, 14 | resize_swapchain = webgl_resize_swapchain, 15 | get_swapchain_width = webgl_get_swapchain_width, 16 | get_swapchain_height = webgl_get_swapchain_height, 17 | flip_z = webgl_flip_z, 18 | set_internal_state = webgl_set_internal_state, 19 | create_texture = webgl_create_texture, 20 | load_texture = webgl_load_texture, 21 | update_texture = webgl_update_texture, 22 | destroy_texture = webgl_destroy_texture, 23 | create_render_texture = webgl_create_render_texture, 24 | destroy_render_target = webgl_destroy_render_target, 25 | set_texture_filter = webgl_set_texture_filter, 26 | load_shader = webgl_load_shader, 27 | destroy_shader = webgl_destroy_shader, 28 | 29 | default_shader_vertex_source = webgl_default_shader_vertex_source, 30 | default_shader_fragment_source = webgl_default_shader_fragment_source, 31 | } 32 | 33 | import "base:runtime" 34 | import gl "vendor:wasm/WebGL" 35 | import hm "handle_map" 36 | import "core:log" 37 | import "core:strings" 38 | import "core:slice" 39 | import la "core:math/linalg" 40 | 41 | _ :: la 42 | 43 | WebGL_State :: struct { 44 | canvas_id: string, 45 | width: int, 46 | height: int, 47 | allocator: runtime.Allocator, 48 | shaders: hm.Handle_Map(WebGL_Shader, Shader_Handle, 1024*10), 49 | vertex_buffer_gpu: gl.Buffer, 50 | textures: hm.Handle_Map(WebGL_Texture, Texture_Handle, 1024*10), 51 | } 52 | 53 | WebGL_Shader_Constant_Buffer :: struct { 54 | buffer: gl.Buffer, 55 | size: int, 56 | block_index: u32, 57 | } 58 | 59 | WebGL_Shader_Constant_Type :: enum { 60 | Uniform, 61 | Block_Variable, 62 | } 63 | 64 | // OpenGL can have constants both in blocks (like constant buffers in D3D11), or as stand-alone 65 | // uniforms. We support both. 66 | WebGL_Shader_Constant :: struct { 67 | type: WebGL_Shader_Constant_Type, 68 | 69 | // if type is Uniform, then this is the uniform loc 70 | // if type is Block_Variable, then this is the block loc 71 | loc: i32, 72 | 73 | // if this is a block variable, then this is the offset to it 74 | block_variable_offset: u32, 75 | 76 | // if type is Uniform, then this contains the GL type of the uniform 77 | uniform_type: gl.Enum, 78 | } 79 | 80 | WebGL_Texture :: struct { 81 | handle: Texture_Handle, 82 | id: u32, 83 | format: Pixel_Format, 84 | } 85 | 86 | WebGL_Texture_Binding :: struct { 87 | loc: i32, 88 | } 89 | 90 | WebGL_Shader :: struct { 91 | handle: Shader_Handle, 92 | 93 | // This is like the "input layout" 94 | vao: u32, 95 | 96 | program: gl.Program, 97 | 98 | constant_buffers: []WebGL_Shader_Constant_Buffer, 99 | constants: []WebGL_Shader_Constant, 100 | texture_bindings: []WebGL_Texture_Binding, 101 | } 102 | 103 | s: ^WebGL_State 104 | 105 | webgl_state_size :: proc() -> int { 106 | return size_of(WebGL_State) 107 | } 108 | 109 | webgl_init :: proc(state: rawptr, window_handle: Window_Handle, swapchain_width, swapchain_height: int, allocator := context.allocator) { 110 | s = (^WebGL_State)(state) 111 | canvas_id := (^HTML_Canvas_ID)(window_handle)^ 112 | s.canvas_id = strings.clone(canvas_id, allocator) 113 | s.width = swapchain_width 114 | s.height = swapchain_height 115 | s.allocator = allocator 116 | 117 | context_ok := gl.CreateCurrentContextById(s.canvas_id, gl.DEFAULT_CONTEXT_ATTRIBUTES) 118 | log.ensuref(context_ok, "Could not create context for canvas ID %s", s.canvas_id) 119 | set_context_ok := gl.SetCurrentContextById(s.canvas_id) 120 | log.ensuref(set_context_ok, "Failed setting context with canvas ID %s", s.canvas_id) 121 | 122 | gl.Enable(gl.DEPTH_TEST) 123 | gl.DepthFunc(gl.GREATER) 124 | 125 | s.vertex_buffer_gpu = gl.CreateBuffer() 126 | gl.BindBuffer(gl.ARRAY_BUFFER, s.vertex_buffer_gpu) 127 | gl.BufferData(gl.ARRAY_BUFFER, VERTEX_BUFFER_MAX, nil, gl.DYNAMIC_DRAW) 128 | 129 | gl.Enable(gl.BLEND) 130 | } 131 | 132 | webgl_shutdown :: proc() { 133 | gl.DeleteBuffer(s.vertex_buffer_gpu) 134 | } 135 | 136 | webgl_clear :: proc(render_texture: Render_Target_Handle, color: Color) { 137 | c := f32_color_from_color(color) 138 | gl.ClearColor(c.r, c.g, c.b, c.a) 139 | gl.ClearDepth(-1) 140 | gl.Clear(u32(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)) 141 | } 142 | 143 | webgl_present :: proc() { 144 | // The browser flips the backbuffer for you 145 | } 146 | 147 | webgl_draw :: proc( 148 | shd: Shader, 149 | render_texture: Render_Target_Handle, 150 | bound_textures: []Texture_Handle, 151 | scissor: Maybe(Rect), 152 | blend_mode: Blend_Mode, 153 | vertex_buffer: []u8, 154 | ) { 155 | gl_shd := hm.get(&s.shaders, shd.handle) 156 | 157 | if gl_shd == nil { 158 | return 159 | } 160 | 161 | switch blend_mode { 162 | case .Alpha: gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 163 | case .Premultiplied_Alpha: gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 164 | } 165 | 166 | gl.EnableVertexAttribArray(0) 167 | gl.EnableVertexAttribArray(1) 168 | gl.EnableVertexAttribArray(2) 169 | 170 | gl.UseProgram(gl_shd.program) 171 | assert(len(shd.constants) == len(gl_shd.constants)) 172 | 173 | cpu_data := shd.constants_data 174 | for cidx in 0.. int { 321 | return s.width 322 | } 323 | 324 | webgl_get_swapchain_height :: proc() -> int { 325 | return s.height 326 | } 327 | 328 | webgl_flip_z :: proc() -> bool { 329 | return false 330 | } 331 | 332 | webgl_set_internal_state :: proc(state: rawptr) { 333 | s = (^GL_State)(state) 334 | } 335 | 336 | create_texture :: proc(width: int, height: int, format: Pixel_Format, data: rawptr) -> Texture_Handle { 337 | id: u32 338 | gl.GenTextures(1, &id) 339 | gl.BindTexture(gl.TEXTURE_2D, id) 340 | 341 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) 342 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) 343 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) 344 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) 345 | 346 | pf := gl_translate_pixel_format(format) 347 | gl.TexImage2D(gl.TEXTURE_2D, 0, pf, i32(width), i32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, data) 348 | 349 | tex := GL_Texture { 350 | id = id, 351 | format = format, 352 | } 353 | 354 | return hm.add(&s.textures, tex) 355 | } 356 | 357 | webgl_create_texture :: proc(width: int, height: int, format: Pixel_Format) -> Texture_Handle { 358 | return create_texture(width, height, format, nil) 359 | } 360 | 361 | webgl_load_texture :: proc(data: []u8, width: int, height: int, format: Pixel_Format) -> Texture_Handle { 362 | return create_texture(width, height, format, raw_data(data)) 363 | } 364 | 365 | webgl_update_texture :: proc(th: Texture_Handle, data: []u8, rect: Rect) -> bool { 366 | tex := hm.get(&s.textures, th) 367 | 368 | if tex == nil { 369 | return false 370 | } 371 | 372 | gl.BindTexture(gl.TEXTURE_2D, tex.id) 373 | gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(rect.x), i32(rect.y), i32(rect.w), i32(rect.h), gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data)) 374 | return true 375 | } 376 | 377 | webgl_destroy_texture :: proc(th: Texture_Handle) { 378 | tex := hm.get(&s.textures, th) 379 | 380 | if tex == nil { 381 | return 382 | } 383 | 384 | gl.DeleteTextures(1, &tex.id) 385 | hm.remove(&s.textures, th) 386 | } 387 | 388 | webgl_create_render_texture :: proc(width: int, height: int) -> (Texture_Handle, Render_Target_Handle) { 389 | return {}, {} 390 | } 391 | 392 | webgl_destroy_render_target :: proc(render_target: Render_Target_Handle) { 393 | 394 | } 395 | 396 | webgl_set_texture_filter :: proc( 397 | th: Texture_Handle, 398 | scale_down_filter: Texture_Filter, 399 | scale_up_filter: Texture_Filter, 400 | mip_filter: Texture_Filter, 401 | ) { 402 | t := hm.get(&s.textures, th) 403 | 404 | if t == nil { 405 | log.error("Trying to set texture filter for invalid texture %v", th) 406 | return 407 | } 408 | 409 | gl.BindTexture(gl.TEXTURE_2D, t.id) 410 | 411 | min_filter: i32 = scale_down_filter == .Point ? gl.NEAREST : gl.LINEAR 412 | mag_filter: i32 = scale_up_filter == .Point ? gl.NEAREST : gl.LINEAR 413 | 414 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min_filter) 415 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag_filter) 416 | } 417 | 418 | Shader_Compile_Result_OK :: struct {} 419 | 420 | Shader_Compile_Result_Error :: string 421 | 422 | Shader_Compile_Result :: union #no_nil { 423 | Shader_Compile_Result_OK, 424 | Shader_Compile_Result_Error, 425 | } 426 | 427 | compile_shader_from_source :: proc(shader_data: []byte, shader_type: gl.Shader_Type, err_buf: []u8, err_msg: ^string) -> (shader_id: u32, ok: bool) { 428 | shader_id = gl.CreateShader(u32(shader_type)) 429 | length := i32(len(shader_data)) 430 | shader_cstr := cstring(raw_data(shader_data)) 431 | gl.ShaderSource(shader_id, 1, &shader_cstr, &length) 432 | gl.CompileShader(shader_id) 433 | 434 | result: i32 435 | gl.GetShaderiv(shader_id, gl.COMPILE_STATUS, &result) 436 | 437 | if result != 1 { 438 | info_len: i32 439 | gl.GetShaderInfoLog(shader_id, i32(len(err_buf)), &info_len, raw_data(err_buf)) 440 | err_msg^ = string(err_buf[:info_len]) 441 | gl.DeleteShader(shader_id) 442 | return 0, false 443 | } 444 | 445 | return shader_id, true 446 | } 447 | 448 | link_shader :: proc(vs_shader: u32, fs_shader: u32, err_buf: []u8, err_msg: ^string) -> (program_id: u32, ok: bool) { 449 | program_id = gl.CreateProgram() 450 | gl.AttachShader(program_id, vs_shader) 451 | gl.AttachShader(program_id, fs_shader) 452 | gl.LinkProgram(program_id) 453 | 454 | result: i32 455 | gl.GetProgramiv(program_id, gl.LINK_STATUS, &result) 456 | 457 | if result != 1 { 458 | info_len: i32 459 | gl.GetProgramInfoLog(program_id, i32(len(err_buf)), &info_len, raw_data(err_buf)) 460 | err_msg^ = string(err_buf[:info_len]) 461 | gl.DeleteProgram(program_id) 462 | return 0, false 463 | } 464 | 465 | return program_id, true 466 | } 467 | 468 | webgl_load_shader :: proc(vs_source: []byte, fs_source: []byte, desc_allocator := frame_allocator, layout_formats: []Pixel_Format = {}) -> (handle: Shader_Handle, desc: Shader_Desc) { 469 | @static err: [1024]u8 470 | err_msg: string 471 | vs_shader, vs_shader_ok := compile_shader_from_source(vs_source, gl.Shader_Type.VERTEX_SHADER, err[:], &err_msg) 472 | 473 | if !vs_shader_ok { 474 | log.error(err_msg) 475 | return {}, {} 476 | } 477 | 478 | fs_shader, fs_shader_ok := compile_shader_from_source(fs_source, gl.Shader_Type.FRAGMENT_SHADER, err[:], &err_msg) 479 | 480 | if !fs_shader_ok { 481 | log.error(err_msg) 482 | return {}, {} 483 | } 484 | 485 | program, program_ok := link_shader(vs_shader, fs_shader, err[:], &err_msg) 486 | 487 | if !program_ok { 488 | log.error(err_msg) 489 | return {}, {} 490 | } 491 | 492 | stride: int 493 | 494 | { 495 | num_attribs: i32 496 | gl.GetProgramiv(program, gl.ACTIVE_ATTRIBUTES, &num_attribs) 497 | desc.inputs = make([]Shader_Input, num_attribs, desc_allocator) 498 | 499 | attrib_name_buf: [256]u8 500 | 501 | for i in 0.. 0 ? layout_formats[loc] : get_shader_input_format(name, type) 536 | desc.inputs[i] = { 537 | name = name, 538 | register = int(loc), 539 | format = format, 540 | type = type, 541 | } 542 | 543 | input_format := get_shader_input_format(name, type) 544 | format_size := pixel_format_size(input_format) 545 | 546 | stride += format_size 547 | } 548 | } 549 | 550 | gl_shd := GL_Shader { 551 | program = program, 552 | } 553 | 554 | gl.GenVertexArrays(1, &gl_shd.vao) 555 | gl.BindVertexArray(gl_shd.vao) 556 | gl.BindBuffer(gl.ARRAY_BUFFER, s.vertex_buffer_gpu) 557 | 558 | offset: int 559 | for idx in 0..= num_active_uniform_blocks { 652 | continue 653 | } 654 | 655 | size: i32 656 | 657 | // TODO investigate if we need std140 layout in the shader or what is fine? 658 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_DATA_SIZE, &size) 659 | 660 | if size == 0 { 661 | log.errorf("Uniform block %v has size 0", name_cstr) 662 | continue 663 | } 664 | 665 | buf: u32 666 | 667 | gl.GenBuffers(1, &buf) 668 | gl.BindBuffer(gl.UNIFORM_BUFFER, buf) 669 | gl.BufferData(gl.UNIFORM_BUFFER, int(size), nil, gl.DYNAMIC_DRAW) 670 | gl.BindBufferBase(gl.UNIFORM_BUFFER, idx, buf) 671 | 672 | gl_shd.constant_buffers[cb_idx] = { 673 | block_index = idx, 674 | buffer = buf, 675 | size = int(size), 676 | } 677 | 678 | num_uniforms: i32 679 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num_uniforms) 680 | 681 | uniform_indices := make([]i32, num_uniforms, frame_allocator) 682 | gl.GetActiveUniformBlockiv(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, raw_data(uniform_indices)) 683 | 684 | for var_idx in 0.. int { 723 | sz: int 724 | switch t { 725 | case gl.FLOAT: sz = 4*1 726 | 727 | case gl.FLOAT_VEC2: sz = 4*2*1 728 | case gl.FLOAT_MAT2: sz = 4*2*2 729 | case gl.FLOAT_MAT2x3: sz = 4*2*3 730 | case gl.FLOAT_MAT2x4: sz = 4*2*4 731 | 732 | case gl.FLOAT_VEC3: sz = 4*3*1 733 | case gl.FLOAT_MAT3x2: sz = 4*3*2 734 | case gl.FLOAT_MAT3: sz = 4*3*3 735 | case gl.FLOAT_MAT3x4: sz = 4*3*4 736 | 737 | case gl.FLOAT_VEC4: sz = 4*4*1 738 | case gl.FLOAT_MAT4x2: sz = 4*4*2 739 | case gl.FLOAT_MAT4x3: sz = 4*4*3 740 | case gl.FLOAT_MAT4: sz = 4*4*4 741 | 742 | case gl.DOUBLE: sz = 8*1 743 | 744 | case gl.DOUBLE_VEC2: sz = 8*2*1 745 | case gl.DOUBLE_MAT2: sz = 8*2*2 746 | case gl.DOUBLE_MAT2x3: sz = 8*2*3 747 | case gl.DOUBLE_MAT2x4: sz = 8*2*4 748 | 749 | case gl.DOUBLE_VEC3: sz = 8*3*1 750 | case gl.DOUBLE_MAT3x2: sz = 8*3*2 751 | case gl.DOUBLE_MAT3: sz = 8*3*3 752 | case gl.DOUBLE_MAT3x4: sz = 8*3*4 753 | 754 | case gl.DOUBLE_VEC4: sz = 8*4*1 755 | case gl.DOUBLE_MAT4x2: sz = 8*4*2 756 | case gl.DOUBLE_MAT4x3: sz = 8*4*3 757 | case gl.DOUBLE_MAT4: sz = 8*4*4 758 | 759 | case gl.BOOL: sz = 4*1 760 | case gl.BOOL_VEC2: sz = 4*2 761 | case gl.BOOL_VEC3: sz = 4*3 762 | case gl.BOOL_VEC4: sz = 4*4 763 | 764 | case gl.INT: sz = 4*1 765 | case gl.INT_VEC2: sz = 4*2 766 | case gl.INT_VEC3: sz = 4*3 767 | case gl.INT_VEC4: sz = 4*4 768 | 769 | case gl.UNSIGNED_INT: sz = 4*1 770 | case gl.UNSIGNED_INT_VEC2: sz = 4*2 771 | case gl.UNSIGNED_INT_VEC3: sz = 4*3 772 | case gl.UNSIGNED_INT_VEC4: sz = 4*4 773 | case: log.errorf("Unhandled uniform type: %x", t) 774 | } 775 | 776 | return sz 777 | } 778 | 779 | gl_translate_pixel_format :: proc(f: Pixel_Format) -> i32 { 780 | switch f { 781 | case .RGBA_32_Float: return gl.RGBA 782 | case .RGB_32_Float: return gl.RGB 783 | case .RG_32_Float: return gl.RG 784 | case .R_32_Float: return gl.R 785 | 786 | case .RGBA_8_Norm: return gl.RGBA8_SNORM 787 | case .RG_8_Norm: return gl.RG8_SNORM 788 | case .R_8_Norm: return gl.R8_SNORM 789 | case .R_8_UInt: return gl.R8_SNORM 790 | 791 | case .Unknown: fallthrough 792 | case: log.error("Unhandled pixel format %v", f) 793 | } 794 | 795 | return 0 796 | } 797 | 798 | 799 | gl_describe_pixel_format :: proc(f: Pixel_Format) -> (format: u32, num_components: i32, normalized: bool) { 800 | switch f { 801 | case .RGBA_32_Float: return gl.FLOAT, 4, false 802 | case .RGB_32_Float: return gl.FLOAT, 3, false 803 | case .RG_32_Float: return gl.FLOAT, 2, false 804 | case .R_32_Float: return gl.FLOAT, 1, false 805 | 806 | case .RGBA_8_Norm: return gl.UNSIGNED_BYTE, 4, true 807 | case .RG_8_Norm: return gl.UNSIGNED_BYTE, 2, true 808 | case .R_8_Norm: return gl.UNSIGNED_BYTE, 1, true 809 | case .R_8_UInt: return gl.BYTE, 1, false 810 | 811 | case .Unknown: 812 | } 813 | 814 | log.errorf("Unknown format %x", format) 815 | return 0, 0, false 816 | } 817 | 818 | webgl_destroy_shader :: proc(h: Shader_Handle) { 819 | shd := hm.get(&s.shaders, h) 820 | 821 | if shd == nil { 822 | log.errorf("Invalid shader: %v", h) 823 | return 824 | } 825 | 826 | delete(shd.constant_buffers, s.allocator) 827 | delete(shd.constants, s.allocator) 828 | delete(shd.texture_bindings, s.allocator) 829 | } 830 | 831 | webgl_default_shader_vertex_source :: proc() -> []byte { 832 | vertex_source := #load("render_backend_gl_default_vertex_shader.glsl") 833 | return vertex_source 834 | } 835 | 836 | webgl_default_shader_fragment_source :: proc() -> []byte { 837 | fragment_source := #load("render_backend_gl_default_fragment_shader.glsl") 838 | return fragment_source 839 | } 840 | 841 | -------------------------------------------------------------------------------- /render_backend_d3d11.odin: -------------------------------------------------------------------------------- 1 | #+build windows 2 | #+vet explicit-allocators 3 | #+private file 4 | 5 | package karl2d 6 | 7 | @(private="package") 8 | RENDER_BACKEND_INTERFACE_D3D11 :: Render_Backend_Interface { 9 | state_size = d3d11_state_size, 10 | init = d3d11_init, 11 | shutdown = d3d11_shutdown, 12 | clear = d3d11_clear, 13 | present = d3d11_present, 14 | draw = d3d11_draw, 15 | resize_swapchain = d3d11_resize_swapchain, 16 | get_swapchain_width = d3d11_get_swapchain_width, 17 | get_swapchain_height = d3d11_get_swapchain_height, 18 | flip_z = d3d11_flip_z, 19 | set_internal_state = d3d11_set_internal_state, 20 | create_texture = d3d11_create_texture, 21 | load_texture = d3d11_load_texture, 22 | update_texture = d3d11_update_texture, 23 | destroy_texture = d3d11_destroy_texture, 24 | create_render_texture = d3d11_create_render_texture, 25 | destroy_render_target = d3d11_destroy_render_target, 26 | set_texture_filter = d3d11_set_texture_filter, 27 | load_shader = d3d11_load_shader, 28 | destroy_shader = d3d11_destroy_shader, 29 | default_shader_vertex_source = d3d11_default_shader_vertex_source, 30 | default_shader_fragment_source = d3d11_default_shader_fragment_source, 31 | } 32 | 33 | import d3d11 "vendor:directx/d3d11" 34 | import dxgi "vendor:directx/dxgi" 35 | import "vendor:directx/d3d_compiler" 36 | import "core:strings" 37 | import "core:log" 38 | import "core:slice" 39 | import "core:mem" 40 | import hm "handle_map" 41 | import "base:runtime" 42 | 43 | d3d11_state_size :: proc() -> int { 44 | return size_of(D3D11_State) 45 | } 46 | 47 | d3d11_init :: proc(state: rawptr, window_handle: Window_Handle, swapchain_width, swapchain_height: int, allocator := context.allocator) { 48 | s = (^D3D11_State)(state) 49 | s.allocator = allocator 50 | s.window_handle = dxgi.HWND(window_handle) 51 | s.width = swapchain_width 52 | s.height = swapchain_height 53 | feature_levels := [?]d3d11.FEATURE_LEVEL{ 54 | ._11_1, 55 | ._11_0, 56 | } 57 | 58 | base_device: ^d3d11.IDevice 59 | base_device_context: ^d3d11.IDeviceContext 60 | 61 | base_device_flags := d3d11.CREATE_DEVICE_FLAGS { 62 | .BGRA_SUPPORT, 63 | } 64 | 65 | when ODIN_DEBUG { 66 | device_flags := base_device_flags + { .DEBUG } 67 | 68 | device_err := ch(d3d11.CreateDevice( 69 | nil, 70 | .HARDWARE, 71 | nil, 72 | device_flags, 73 | &feature_levels[0], len(feature_levels), 74 | d3d11.SDK_VERSION, &base_device, nil, &base_device_context)) 75 | 76 | if u32(device_err) == 0x887a002d { 77 | log.error("You're running in debug mode. So we are trying to create a debug D3D11 device. But you don't have DirectX SDK installed, so we can't enable debug layers. Creating a device without debug layers (you'll get no good D3D11 errors).") 78 | 79 | ch(d3d11.CreateDevice( 80 | nil, 81 | .HARDWARE, 82 | nil, 83 | base_device_flags, 84 | &feature_levels[0], len(feature_levels), 85 | d3d11.SDK_VERSION, &base_device, nil, &base_device_context)) 86 | } else { 87 | ch(base_device->QueryInterface(d3d11.IInfoQueue_UUID, (^rawptr)(&s.info_queue))) 88 | } 89 | } else { 90 | ch(d3d11.CreateDevice( 91 | nil, 92 | .HARDWARE, 93 | nil, 94 | base_device_flags, 95 | &feature_levels[0], len(feature_levels), 96 | d3d11.SDK_VERSION, &base_device, nil, &base_device_context)) 97 | } 98 | 99 | ch(base_device->QueryInterface(d3d11.IDevice_UUID, (^rawptr)(&s.device))) 100 | ch(base_device_context->QueryInterface(d3d11.IDeviceContext_UUID, (^rawptr)(&s.device_context))) 101 | dxgi_device: ^dxgi.IDevice 102 | ch(s.device->QueryInterface(dxgi.IDevice_UUID, (^rawptr)(&dxgi_device))) 103 | base_device->Release() 104 | base_device_context->Release() 105 | 106 | ch(dxgi_device->GetAdapter(&s.dxgi_adapter)) 107 | 108 | create_swapchain(swapchain_width, swapchain_height) 109 | 110 | rasterizer_desc := d3d11.RASTERIZER_DESC{ 111 | FillMode = .SOLID, 112 | CullMode = .BACK, 113 | ScissorEnable = true, 114 | } 115 | ch(s.device->CreateRasterizerState(&rasterizer_desc, &s.rasterizer_state)) 116 | 117 | depth_stencil_desc := d3d11.DEPTH_STENCIL_DESC{ 118 | DepthEnable = true, 119 | DepthWriteMask = .ALL, 120 | DepthFunc = .LESS, 121 | } 122 | ch(s.device->CreateDepthStencilState(&depth_stencil_desc, &s.depth_stencil_state)) 123 | 124 | vertex_buffer_desc := d3d11.BUFFER_DESC{ 125 | ByteWidth = VERTEX_BUFFER_MAX, 126 | Usage = .DYNAMIC, 127 | BindFlags = {.VERTEX_BUFFER}, 128 | CPUAccessFlags = {.WRITE}, 129 | } 130 | ch(s.device->CreateBuffer(&vertex_buffer_desc, nil, &s.vertex_buffer_gpu)) 131 | 132 | blend_alpha_desc := d3d11.BLEND_DESC { 133 | RenderTarget = { 134 | 0 = { 135 | BlendEnable = true, 136 | SrcBlend = .SRC_ALPHA, 137 | DestBlend = .INV_SRC_ALPHA, 138 | BlendOp = .ADD, 139 | SrcBlendAlpha = .SRC_ALPHA, 140 | DestBlendAlpha = .INV_SRC_ALPHA, 141 | BlendOpAlpha = .ADD, 142 | RenderTargetWriteMask = u8(d3d11.COLOR_WRITE_ENABLE_ALL), 143 | }, 144 | }, 145 | } 146 | 147 | ch(s.device->CreateBlendState(&blend_alpha_desc, &s.blend_state_alpha)) 148 | 149 | blend_premultiplied_alpha_desc := d3d11.BLEND_DESC { 150 | RenderTarget = { 151 | 0 = { 152 | BlendEnable = true, 153 | SrcBlend = .ONE, 154 | DestBlend = .INV_SRC_ALPHA, 155 | BlendOp = .ADD, 156 | SrcBlendAlpha = .ONE, 157 | DestBlendAlpha = .INV_SRC_ALPHA, 158 | BlendOpAlpha = .ADD, 159 | RenderTargetWriteMask = u8(d3d11.COLOR_WRITE_ENABLE_ALL), 160 | }, 161 | }, 162 | } 163 | 164 | ch(s.device->CreateBlendState(&blend_premultiplied_alpha_desc, &s.blend_state_premultiplied_alpha)) 165 | } 166 | 167 | d3d11_shutdown :: proc() { 168 | s.framebuffer_view->Release() 169 | s.depth_buffer_view->Release() 170 | s.depth_buffer->Release() 171 | s.framebuffer->Release() 172 | s.device_context->Release() 173 | s.vertex_buffer_gpu->Release() 174 | s.depth_stencil_state->Release() 175 | s.rasterizer_state->Release() 176 | s.swapchain->Release() 177 | s.blend_state_alpha->Release() 178 | s.blend_state_premultiplied_alpha->Release() 179 | s.dxgi_adapter->Release() 180 | 181 | when ODIN_DEBUG { 182 | debug: ^d3d11.IDebug 183 | 184 | if ch(s.device->QueryInterface(d3d11.IDebug_UUID, (^rawptr)(&debug))) >= 0 { 185 | ch(debug->ReportLiveDeviceObjects({.DETAIL, .IGNORE_INTERNAL})) 186 | log_messages() 187 | } 188 | 189 | debug->Release() 190 | } 191 | 192 | s.device->Release() 193 | s.info_queue->Release() 194 | } 195 | 196 | d3d11_clear :: proc(render_target: Render_Target_Handle, color: Color) { 197 | c := f32_color_from_color(color) 198 | 199 | if rt := hm.get(&s.render_targets, render_target); rt != nil { 200 | s.device_context->ClearRenderTargetView(rt.render_target_view, &c) 201 | s.device_context->ClearDepthStencilView(rt.depth_stencil_texture_view, {.DEPTH}, 1, 0) 202 | } else { 203 | s.device_context->ClearRenderTargetView(s.framebuffer_view, &c) 204 | s.device_context->ClearDepthStencilView(s.depth_buffer_view, {.DEPTH}, 1, 0) 205 | } 206 | } 207 | 208 | d3d11_present :: proc() { 209 | ch(s.swapchain->Present(1, {})) 210 | } 211 | 212 | d3d11_draw :: proc( 213 | shd: Shader, 214 | render_target: Render_Target_Handle, 215 | bound_textures: []Texture_Handle, 216 | scissor: Maybe(Rect), 217 | blend_mode: Blend_Mode, 218 | vertex_buffer: []u8, 219 | ) { 220 | if len(vertex_buffer) == 0 { 221 | return 222 | } 223 | 224 | d3d_shd := hm.get(&s.shaders, shd.handle) 225 | 226 | if d3d_shd == nil { 227 | log.error("Trying to draw with invalid shader %v", shd.handle) 228 | return 229 | } 230 | 231 | dc := s.device_context 232 | 233 | vb_data: d3d11.MAPPED_SUBRESOURCE 234 | ch(dc->Map(s.vertex_buffer_gpu, 0, .WRITE_DISCARD, {}, &vb_data)) 235 | { 236 | gpu_map := slice.from_ptr((^u8)(vb_data.pData), VERTEX_BUFFER_MAX) 237 | copy( 238 | gpu_map, 239 | vertex_buffer, 240 | ) 241 | } 242 | dc->Unmap(s.vertex_buffer_gpu, 0) 243 | 244 | dc->IASetPrimitiveTopology(.TRIANGLELIST) 245 | 246 | dc->IASetInputLayout(d3d_shd.input_layout) 247 | vertex_buffer_offset: u32 248 | vertex_buffer_stride := u32(shd.vertex_size) 249 | dc->IASetVertexBuffers(0, 1, &s.vertex_buffer_gpu, &vertex_buffer_stride, &vertex_buffer_offset) 250 | 251 | dc->VSSetShader(d3d_shd.vertex_shader, nil, 0) 252 | 253 | assert(len(shd.constants) == len(d3d_shd.constants)) 254 | 255 | maps := make([]rawptr, len(d3d_shd.constant_buffers), frame_allocator) 256 | 257 | cpu_data := shd.constants_data 258 | for cidx in 0..Map(gpu_data, 0, .WRITE_DISCARD, {}, &map_data)) 274 | maps[gpu_loc.buffer_idx] = map_data.pData 275 | } 276 | 277 | data_slice := slice.bytes_from_ptr(maps[gpu_loc.buffer_idx], gpu_buffer_info.size) 278 | dst := data_slice[gpu_loc.offset:gpu_loc.offset+u32(cpu_loc.size)] 279 | src := cpu_data[cpu_loc.offset:cpu_loc.offset+cpu_loc.size] 280 | copy(dst, src) 281 | } 282 | 283 | for &cb, cb_idx in d3d_shd.constant_buffers { 284 | if .Vertex in cb.bound_shaders { 285 | dc->VSSetConstantBuffers(cb.bind_point, 1, &cb.gpu_data) 286 | } 287 | 288 | if .Pixel in cb.bound_shaders { 289 | dc->PSSetConstantBuffers(cb.bind_point, 1, &cb.gpu_data) 290 | } 291 | 292 | if maps[cb_idx] != nil { 293 | dc->Unmap(cb.gpu_data, 0) 294 | maps[cb_idx] = nil 295 | } 296 | } 297 | 298 | viewport := d3d11.VIEWPORT{ 299 | 0, 0, 300 | f32(s.width), f32(s.height), 301 | 0, 1, 302 | } 303 | 304 | dc->RSSetViewports(1, &viewport) 305 | dc->RSSetState(s.rasterizer_state) 306 | 307 | scissor_rect := d3d11.RECT { 308 | right = i32(s.width), 309 | bottom = i32(s.height), 310 | } 311 | 312 | if sciss, sciss_ok := scissor.?; sciss_ok { 313 | scissor_rect = d3d11.RECT { 314 | left = i32(sciss.x), 315 | top = i32(sciss.y), 316 | right = i32(sciss.x + sciss.w), 317 | bottom = i32(sciss.y + sciss.h), 318 | } 319 | } 320 | 321 | dc->RSSetScissorRects(1, &scissor_rect) 322 | 323 | dc->PSSetShader(d3d_shd.pixel_shader, nil, 0) 324 | 325 | if len(bound_textures) == len(d3d_shd.texture_bindings) { 326 | for t, t_idx in bound_textures { 327 | d3d_t := d3d_shd.texture_bindings[t_idx] 328 | 329 | if t := hm.get(&s.textures, t); t != nil { 330 | dc->PSSetShaderResources(d3d_t.bind_point, 1, &t.view) 331 | dc->PSSetSamplers(d3d_t.sampler_bind_point, 1, &t.sampler) 332 | } 333 | } 334 | } 335 | 336 | if rt := hm.get(&s.render_targets, render_target); rt != nil { 337 | dc->OMSetRenderTargets(1, &rt.render_target_view, rt.depth_stencil_texture_view) 338 | } else { 339 | dc->OMSetRenderTargets(1, &s.framebuffer_view, s.depth_buffer_view) 340 | } 341 | 342 | dc->OMSetDepthStencilState(s.depth_stencil_state, 0) 343 | 344 | switch blend_mode { 345 | case .Alpha: 346 | dc->OMSetBlendState(s.blend_state_alpha, nil, ~u32(0)) 347 | case .Premultiplied_Alpha: 348 | dc->OMSetBlendState(s.blend_state_premultiplied_alpha, nil, ~u32(0)) 349 | } 350 | dc->Draw(u32(len(vertex_buffer)/shd.vertex_size), 0) 351 | dc->OMSetRenderTargets(0, nil, nil) 352 | log_messages() 353 | } 354 | 355 | d3d11_resize_swapchain :: proc(w, h: int) { 356 | s.depth_buffer->Release() 357 | s.depth_buffer_view->Release() 358 | s.framebuffer->Release() 359 | s.framebuffer_view->Release() 360 | s.swapchain->Release() 361 | s.width = w 362 | s.height = h 363 | 364 | create_swapchain(w, h) 365 | } 366 | 367 | d3d11_get_swapchain_width :: proc() -> int { 368 | return s.width 369 | } 370 | 371 | d3d11_get_swapchain_height :: proc() -> int { 372 | return s.height 373 | } 374 | 375 | d3d11_flip_z :: proc() -> bool { 376 | return true 377 | } 378 | 379 | d3d11_set_internal_state :: proc(state: rawptr) { 380 | s = (^D3D11_State)(state) 381 | } 382 | 383 | create_texture :: proc( 384 | width: int, 385 | height: int, 386 | format: Pixel_Format, 387 | data: rawptr, 388 | ) -> ( 389 | Texture_Handle, 390 | ) { 391 | texture_desc := d3d11.TEXTURE2D_DESC{ 392 | Width = u32(width), 393 | Height = u32(height), 394 | MipLevels = 1, 395 | ArraySize = 1, 396 | Format = dxgi_format_from_pixel_format(format), 397 | SampleDesc = {Count = 1}, 398 | Usage = .DEFAULT, 399 | BindFlags = {.SHADER_RESOURCE}, 400 | } 401 | 402 | texture: ^d3d11.ITexture2D 403 | 404 | if data != nil { 405 | texture_data := d3d11.SUBRESOURCE_DATA{ 406 | pSysMem = data, 407 | SysMemPitch = u32(width * pixel_format_size(format)), 408 | } 409 | 410 | s.device->CreateTexture2D(&texture_desc, &texture_data, &texture) 411 | } else { 412 | s.device->CreateTexture2D(&texture_desc, nil, &texture) 413 | } 414 | 415 | texture_view: ^d3d11.IShaderResourceView 416 | s.device->CreateShaderResourceView(texture, nil, &texture_view) 417 | 418 | tex := D3D11_Texture { 419 | tex = texture, 420 | format = format, 421 | view = texture_view, 422 | sampler = create_sampler(.MIN_MAG_MIP_POINT), 423 | } 424 | 425 | return hm.add(&s.textures, tex) 426 | } 427 | 428 | d3d11_create_texture :: proc(width: int, height: int, format: Pixel_Format) -> Texture_Handle { 429 | return create_texture(width, height, format, nil) 430 | } 431 | 432 | d3d11_create_render_texture :: proc(width: int, height: int) -> (Texture_Handle, Render_Target_Handle) { 433 | texture_desc := d3d11.TEXTURE2D_DESC{ 434 | Width = u32(width), 435 | Height = u32(height), 436 | MipLevels = 1, 437 | ArraySize = 1, 438 | Format = dxgi_format_from_pixel_format(.RGBA_32_Float), 439 | SampleDesc = {Count = 1}, 440 | Usage = .DEFAULT, 441 | BindFlags = {.SHADER_RESOURCE, .RENDER_TARGET}, 442 | } 443 | 444 | texture: ^d3d11.ITexture2D 445 | ch(s.device->CreateTexture2D(&texture_desc, nil, &texture)) 446 | 447 | texture_view: ^d3d11.IShaderResourceView 448 | ch(s.device->CreateShaderResourceView(texture, nil, &texture_view)) 449 | 450 | depth_stencil_desc := d3d11.TEXTURE2D_DESC{ 451 | Width = u32(width), 452 | Height = u32(height), 453 | MipLevels = 1, 454 | ArraySize = 1, 455 | Format = .D24_UNORM_S8_UINT, 456 | SampleDesc = {Count = 1}, 457 | Usage = .DEFAULT, 458 | BindFlags = {.DEPTH_STENCIL}, 459 | } 460 | 461 | depth_stencil_texture: ^d3d11.ITexture2D 462 | ch(ch(s.device->CreateTexture2D(&depth_stencil_desc, nil, &depth_stencil_texture))) 463 | 464 | depth_stencil_texture_view: ^d3d11.IDepthStencilView 465 | ch(s.device->CreateDepthStencilView(depth_stencil_texture, nil, &depth_stencil_texture_view)) 466 | 467 | render_target_view_desc := d3d11.RENDER_TARGET_VIEW_DESC { 468 | Format = texture_desc.Format, 469 | ViewDimension = .TEXTURE2D, 470 | } 471 | 472 | render_target_view: ^d3d11.IRenderTargetView 473 | 474 | ch(s.device->CreateRenderTargetView(texture, &render_target_view_desc, &render_target_view)) 475 | 476 | d3d11_texture := D3D11_Texture { 477 | tex = texture, 478 | view = texture_view, 479 | format = .RGBA_32_Float, 480 | sampler = create_sampler(.MIN_MAG_MIP_POINT), 481 | } 482 | 483 | d3d11_render_target := D3D11_Render_Target { 484 | depth_stencil_texture = depth_stencil_texture, 485 | depth_stencil_texture_view = depth_stencil_texture_view, 486 | render_target_view = render_target_view, 487 | } 488 | 489 | return hm.add(&s.textures, d3d11_texture), hm.add(&s.render_targets, d3d11_render_target) 490 | } 491 | 492 | d3d11_destroy_render_target :: proc(render_target: Render_Target_Handle) { 493 | if rt := hm.get(&s.render_targets, render_target); rt != nil { 494 | rt.depth_stencil_texture->Release() 495 | rt.depth_stencil_texture_view->Release() 496 | rt.render_target_view->Release() 497 | } 498 | 499 | hm.remove(&s.render_targets, render_target) 500 | } 501 | 502 | d3d11_load_texture :: proc(data: []u8, width: int, height: int, format: Pixel_Format) -> Texture_Handle { 503 | return create_texture(width, height, format, raw_data(data)) 504 | } 505 | 506 | d3d11_update_texture :: proc(th: Texture_Handle, data: []u8, rect: Rect) -> bool { 507 | tex := hm.get(&s.textures, th) 508 | 509 | if tex == nil || tex.tex == nil { 510 | log.errorf("Trying to update texture %v with new data, but it is invalid.", th) 511 | return false 512 | } 513 | 514 | box := d3d11.BOX { 515 | left = u32(rect.x), 516 | top = u32(rect.y), 517 | bottom = u32(rect.y + rect.h), 518 | right = u32(rect.x + rect.w), 519 | back = 1, 520 | front = 0, 521 | } 522 | 523 | row_pitch := pixel_format_size(tex.format) * int(rect.w) 524 | s.device_context->UpdateSubresource(tex.tex, 0, &box, raw_data(data), u32(row_pitch), 0) 525 | return true 526 | } 527 | 528 | d3d11_destroy_texture :: proc(th: Texture_Handle) { 529 | if t := hm.get(&s.textures, th); t != nil { 530 | t.tex->Release() 531 | t.view->Release() 532 | 533 | if t.sampler != nil { 534 | t.sampler->Release() 535 | } 536 | } 537 | 538 | hm.remove(&s.textures, th) 539 | } 540 | 541 | d3d11_set_texture_filter :: proc( 542 | th: Texture_Handle, 543 | scale_down_filter: Texture_Filter, 544 | scale_up_filter: Texture_Filter, 545 | mip_filter: Texture_Filter, 546 | ) { 547 | t := hm.get(&s.textures, th) 548 | 549 | if t == nil { 550 | log.error("Trying to set texture filter for invalid texture %v", th) 551 | return 552 | } 553 | 554 | d := scale_down_filter 555 | u := scale_up_filter 556 | m := mip_filter 557 | f: d3d11.FILTER 558 | 559 | if d == .Point && u == .Point && m == .Point { 560 | f = .MIN_MAG_MIP_POINT 561 | } else if d == .Linear && u == .Linear && m == .Linear { 562 | f = .MIN_MAG_MIP_LINEAR 563 | } else if d == .Point && u == .Point && m == .Linear { 564 | f = .MIN_MAG_POINT_MIP_LINEAR 565 | } else if d == .Point && u == .Linear && m == .Linear { 566 | f = .MIN_POINT_MAG_MIP_LINEAR 567 | } else if d == .Linear && u == .Linear && m == .Linear { 568 | f = .MIN_MAG_MIP_LINEAR 569 | } else if d == .Linear && u == .Linear && m == .Point { 570 | f = .MIN_MAG_LINEAR_MIP_POINT 571 | } else if d == .Linear && u == .Point && m == .Point { 572 | f = .MIN_LINEAR_MAG_MIP_POINT 573 | } else if d == .Linear && u == .Point && m == .Linear { 574 | f = .MIN_LINEAR_MAG_POINT_MIP_LINEAR 575 | } else if d == .Point && u == .Linear && m == .Point { 576 | f = .MIN_POINT_MAG_LINEAR_MIP_POINT 577 | } 578 | 579 | if t.sampler != nil { 580 | t.sampler->Release() 581 | } 582 | 583 | t.sampler = create_sampler(f) 584 | } 585 | 586 | create_sampler :: proc(filter: d3d11.FILTER) -> ^d3d11.ISamplerState { 587 | sampler_desc := d3d11.SAMPLER_DESC{ 588 | Filter = filter, 589 | AddressU = .WRAP, 590 | AddressV = .WRAP, 591 | AddressW = .WRAP, 592 | ComparisonFunc = .NEVER, 593 | } 594 | 595 | smp: ^d3d11.ISamplerState 596 | ch(s.device->CreateSamplerState(&sampler_desc, &smp)) 597 | return smp 598 | } 599 | 600 | d3d11_load_shader :: proc( 601 | vs_source: []byte, 602 | ps_source: []byte, 603 | desc_allocator := frame_allocator, 604 | layout_formats: []Pixel_Format = {}, 605 | ) -> ( 606 | handle: Shader_Handle, 607 | desc: Shader_Desc, 608 | ) { 609 | vs_blob: ^d3d11.IBlob 610 | vs_blob_errors: ^d3d11.IBlob 611 | ch(d3d_compiler.Compile(raw_data(vs_source), len(vs_source), nil, nil, nil, "vs_main", "vs_5_0", 0, 0, &vs_blob, &vs_blob_errors)) 612 | 613 | if vs_blob_errors != nil { 614 | log.error("Failed compiling shader:") 615 | log.error(strings.string_from_ptr((^u8)(vs_blob_errors->GetBufferPointer()), int(vs_blob_errors->GetBufferSize()))) 616 | return 617 | } 618 | 619 | // VERTEX SHADER 620 | 621 | vertex_shader: ^d3d11.IVertexShader 622 | ch(s.device->CreateVertexShader(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), nil, &vertex_shader)) 623 | 624 | vs_ref: ^d3d11.IShaderReflection 625 | ch(d3d_compiler.Reflect(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), d3d11.ID3D11ShaderReflection_UUID, (^rawptr)(&vs_ref))) 626 | 627 | vs_desc: d3d11.SHADER_DESC 628 | ch(vs_ref->GetDesc(&vs_desc)) 629 | 630 | { 631 | desc.inputs = make([]Shader_Input, vs_desc.InputParameters, desc_allocator) 632 | assert(len(layout_formats) == 0 || len(layout_formats) == len(desc.inputs)) 633 | 634 | for in_idx in 0..GetInputParameterDesc(in_idx, &in_desc)) < 0 { 638 | log.errorf("Invalid shader input: %v", in_idx) 639 | continue 640 | } 641 | 642 | type: Shader_Input_Type 643 | 644 | if in_desc.SemanticIndex > 0 { 645 | log.errorf("Matrix shader input types not yet implemented") 646 | continue 647 | } 648 | 649 | switch in_desc.ComponentType { 650 | case .UNKNOWN: log.errorf("Unknown component type") 651 | case .UINT32: log.errorf("Not implemented") 652 | case .SINT32: log.errorf("Not implemented") 653 | case .FLOAT32: 654 | switch in_desc.Mask { 655 | case 0: log.errorf("Invalid input mask"); continue 656 | case 1: type = .F32 657 | case 3: type = .Vec2 658 | case 7: type = .Vec3 659 | case 15: type = .Vec4 660 | } 661 | } 662 | 663 | name := strings.clone_from_cstring(in_desc.SemanticName, desc_allocator) 664 | 665 | format := len(layout_formats) > 0 ? layout_formats[in_idx] : get_shader_input_format(name, type) 666 | desc.inputs[in_idx] = { 667 | name = name, 668 | register = int(in_idx), 669 | format = format, 670 | type = type, 671 | } 672 | } 673 | } 674 | 675 | constant_descs := make([dynamic]Shader_Constant_Desc, desc_allocator) 676 | d3d_constants := make([dynamic]D3D11_Shader_Constant, s.allocator) 677 | d3d_constant_buffers := make([dynamic]D3D11_Shader_Constant_Buffer, s.allocator) 678 | d3d_texture_bindings := make([dynamic]D3D11_Texture_Binding, s.allocator) 679 | texture_bindpoint_descs := make([dynamic]Shader_Texture_Bindpoint_Desc, desc_allocator) 680 | reflect_shader_constants( 681 | vs_desc, 682 | vs_ref, 683 | &constant_descs, 684 | &d3d_constants, 685 | &d3d_constant_buffers, 686 | &d3d_texture_bindings, 687 | &texture_bindpoint_descs, 688 | desc_allocator, 689 | .Vertex, 690 | ) 691 | 692 | input_layout_desc := make([]d3d11.INPUT_ELEMENT_DESC, len(desc.inputs), frame_allocator) 693 | 694 | for idx in 0..CreateInputLayout(raw_data(input_layout_desc), u32(len(input_layout_desc)), vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), &input_layout)) 706 | 707 | // PIXEL SHADER 708 | 709 | ps_blob: ^d3d11.IBlob 710 | ps_blob_errors: ^d3d11.IBlob 711 | ch(d3d_compiler.Compile(raw_data(ps_source), len(ps_source), nil, nil, nil, "ps_main", "ps_5_0", 0, 0, &ps_blob, &ps_blob_errors)) 712 | 713 | if ps_blob_errors != nil { 714 | log.error("Failed compiling shader:") 715 | log.error(strings.string_from_ptr((^u8)(ps_blob_errors->GetBufferPointer()), int(ps_blob_errors->GetBufferSize()))) 716 | return 717 | } 718 | 719 | pixel_shader: ^d3d11.IPixelShader 720 | ch(s.device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), nil, &pixel_shader)) 721 | 722 | ps_ref: ^d3d11.IShaderReflection 723 | ch(d3d_compiler.Reflect(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), d3d11.ID3D11ShaderReflection_UUID, (^rawptr)(&ps_ref))) 724 | 725 | ps_desc: d3d11.SHADER_DESC 726 | ch(ps_ref->GetDesc(&ps_desc)) 727 | 728 | reflect_shader_constants( 729 | ps_desc, 730 | ps_ref, 731 | &constant_descs, 732 | &d3d_constants, 733 | &d3d_constant_buffers, 734 | &d3d_texture_bindings, 735 | &texture_bindpoint_descs, 736 | desc_allocator, 737 | .Pixel, 738 | ) 739 | 740 | // Done with vertex and pixel shader. Just combine all the state. 741 | 742 | desc.constants = constant_descs[:] 743 | desc.texture_bindpoints = texture_bindpoint_descs[:] 744 | 745 | d3d_shd := D3D11_Shader { 746 | constants = d3d_constants[:], 747 | constant_buffers = d3d_constant_buffers[:], 748 | vertex_shader = vertex_shader, 749 | pixel_shader = pixel_shader, 750 | input_layout = input_layout, 751 | texture_bindings = d3d_texture_bindings[:], 752 | } 753 | 754 | h := hm.add(&s.shaders, d3d_shd) 755 | return h, desc 756 | } 757 | 758 | D3D11_Shader_Type :: enum { 759 | Vertex, 760 | Pixel, 761 | } 762 | 763 | reflect_shader_constants :: proc( 764 | d3d_desc: d3d11.SHADER_DESC, 765 | ref: ^d3d11.IShaderReflection, 766 | constant_descs: ^[dynamic]Shader_Constant_Desc, 767 | d3d_constants: ^[dynamic]D3D11_Shader_Constant, 768 | d3d_constant_buffers: ^[dynamic]D3D11_Shader_Constant_Buffer, 769 | d3d_texture_bindings: ^[dynamic]D3D11_Texture_Binding, 770 | texture_bindpoint_descs: ^[dynamic]Shader_Texture_Bindpoint_Desc, 771 | desc_allocator: runtime.Allocator, 772 | shader_type: D3D11_Shader_Type, 773 | ) { 774 | found_sampler_bindpoints := make([dynamic]u32, frame_allocator) 775 | 776 | for br_idx in 0..GetResourceBindingDesc(br_idx, &bind_desc) 779 | 780 | #partial switch bind_desc.Type { 781 | case .SAMPLER: 782 | append(&found_sampler_bindpoints, bind_desc.BindPoint) 783 | 784 | case .TEXTURE: 785 | append(d3d_texture_bindings, D3D11_Texture_Binding { 786 | bind_point = bind_desc.BindPoint, 787 | }) 788 | 789 | append(texture_bindpoint_descs, Shader_Texture_Bindpoint_Desc { 790 | name = strings.clone_from_cstring(bind_desc.Name, desc_allocator), 791 | }) 792 | 793 | case .CBUFFER: 794 | cb_info := ref->GetConstantBufferByName(bind_desc.Name) 795 | 796 | if cb_info == nil { 797 | continue 798 | } 799 | 800 | cb_desc: d3d11.SHADER_BUFFER_DESC 801 | cb_info->GetDesc(&cb_desc) 802 | 803 | if cb_desc.Size == 0 { 804 | continue 805 | } 806 | 807 | constant_buffer_desc := d3d11.BUFFER_DESC{ 808 | ByteWidth = cb_desc.Size, 809 | Usage = .DYNAMIC, 810 | BindFlags = {.CONSTANT_BUFFER}, 811 | CPUAccessFlags = {.WRITE}, 812 | } 813 | buffer_idx := -1 814 | 815 | for &existing, existing_idx in d3d_constant_buffers { 816 | if existing.bind_point == bind_desc.BindPoint { 817 | existing.bound_shaders += {shader_type} 818 | buffer_idx = existing_idx 819 | break 820 | } 821 | } 822 | 823 | if buffer_idx == -1 { 824 | buffer_idx = len(d3d_constant_buffers) 825 | 826 | buf := D3D11_Shader_Constant_Buffer { 827 | bound_shaders = {shader_type}, 828 | } 829 | 830 | ch(s.device->CreateBuffer(&constant_buffer_desc, nil, &buf.gpu_data)) 831 | buf.size = int(cb_desc.Size) 832 | buf.bind_point = bind_desc.BindPoint 833 | append(d3d_constant_buffers, buf) 834 | } 835 | 836 | for var_idx in 0..GetVariableByIndex(var_idx) 838 | 839 | if var_info == nil { 840 | continue 841 | } 842 | 843 | var_desc: d3d11.SHADER_VARIABLE_DESC 844 | var_info->GetDesc(&var_desc) 845 | 846 | if var_desc.Name != "" { 847 | append(constant_descs, Shader_Constant_Desc { 848 | name = strings.clone_from_cstring(var_desc.Name, desc_allocator), 849 | size = int(var_desc.Size), 850 | }) 851 | 852 | append(d3d_constants, D3D11_Shader_Constant { 853 | buffer_idx = u32(buffer_idx), 854 | offset = var_desc.StartOffset, 855 | }) 856 | } 857 | } 858 | case: 859 | log.errorf("Type is %v", bind_desc.Type) 860 | } 861 | } 862 | 863 | // Make sure each texture has a sampler. In GL samplers are associated with textures. In D3D11 864 | // several textures can use a single sampler. We don't want this as we want to be able to 865 | // configure filters etc on a per-texture level. Since two textures can arrive at a draw call 866 | // with different filters set, if they use the same sampler, then it will be impossible to set 867 | // that filtering up. 868 | for &t, t_idx in d3d_texture_bindings { 869 | found := false 870 | 871 | for sampler_bindpoint in found_sampler_bindpoints { 872 | if t.bind_point == sampler_bindpoint { 873 | t.sampler_bind_point = sampler_bindpoint 874 | found = true 875 | break 876 | } 877 | } 878 | 879 | if !found { 880 | log.errorf( 881 | "Texture %v at bindpoint %v does not have a dedicated sampler at " + 882 | "the sampler register with the same bindpoint number. This is required to " + 883 | "in order to make D3D11 behave the same way as OpenGL etc", 884 | texture_bindpoint_descs[t_idx].name, 885 | t.bind_point, 886 | ) 887 | } 888 | } 889 | } 890 | 891 | d3d11_destroy_shader :: proc(h: Shader_Handle) { 892 | shd := hm.get(&s.shaders, h) 893 | 894 | if shd == nil { 895 | log.errorf("Invalid shader: %v", h) 896 | return 897 | } 898 | 899 | shd.input_layout->Release() 900 | shd.vertex_shader->Release() 901 | shd.pixel_shader->Release() 902 | 903 | for c in shd.constant_buffers { 904 | if c.gpu_data != nil { 905 | c.gpu_data->Release() 906 | } 907 | } 908 | 909 | delete(shd.texture_bindings, s.allocator) 910 | delete(shd.constants, s.allocator) 911 | delete(shd.constant_buffers, s.allocator) 912 | hm.remove(&s.shaders, h) 913 | } 914 | 915 | // API END 916 | 917 | s: ^D3D11_State 918 | 919 | D3D11_Shader_Constant_Buffer :: struct { 920 | gpu_data: ^d3d11.IBuffer, 921 | size: int, 922 | bound_shaders: bit_set[D3D11_Shader_Type], 923 | bind_point: u32, 924 | } 925 | 926 | D3D11_Texture_Binding :: struct { 927 | bind_point: u32, 928 | sampler_bind_point: u32, 929 | } 930 | 931 | D3D11_Shader_Constant :: struct { 932 | buffer_idx: u32, 933 | offset: u32, 934 | } 935 | 936 | D3D11_Shader :: struct { 937 | handle: Shader_Handle, 938 | vertex_shader: ^d3d11.IVertexShader, 939 | pixel_shader: ^d3d11.IPixelShader, 940 | input_layout: ^d3d11.IInputLayout, 941 | constant_buffers: []D3D11_Shader_Constant_Buffer, 942 | constants: []D3D11_Shader_Constant, 943 | texture_bindings: []D3D11_Texture_Binding, 944 | } 945 | 946 | D3D11_State :: struct { 947 | allocator: runtime.Allocator, 948 | 949 | window_handle: dxgi.HWND, 950 | width: int, 951 | height: int, 952 | 953 | dxgi_adapter: ^dxgi.IAdapter, 954 | swapchain: ^dxgi.ISwapChain1, 955 | framebuffer_view: ^d3d11.IRenderTargetView, 956 | depth_buffer_view: ^d3d11.IDepthStencilView, 957 | device_context: ^d3d11.IDeviceContext, 958 | depth_stencil_state: ^d3d11.IDepthStencilState, 959 | rasterizer_state: ^d3d11.IRasterizerState, 960 | device: ^d3d11.IDevice, 961 | depth_buffer: ^d3d11.ITexture2D, 962 | framebuffer: ^d3d11.ITexture2D, 963 | blend_state_alpha: ^d3d11.IBlendState, 964 | blend_state_premultiplied_alpha: ^d3d11.IBlendState, 965 | 966 | textures: hm.Handle_Map(D3D11_Texture, Texture_Handle, 1024*10), 967 | render_targets: hm.Handle_Map(D3D11_Render_Target, Render_Target_Handle, 1024*10), 968 | shaders: hm.Handle_Map(D3D11_Shader, Shader_Handle, 1024*10), 969 | 970 | info_queue: ^d3d11.IInfoQueue, 971 | vertex_buffer_gpu: ^d3d11.IBuffer, 972 | 973 | all_samplers: map[^d3d11.ISamplerState]struct{}, 974 | } 975 | 976 | create_swapchain :: proc(w, h: int) { 977 | swapchain_desc := dxgi.SWAP_CHAIN_DESC1 { 978 | Width = u32(w), 979 | Height = u32(h), 980 | Format = .B8G8R8A8_UNORM, 981 | SampleDesc = { 982 | Count = 1, 983 | }, 984 | BufferUsage = {.RENDER_TARGET_OUTPUT}, 985 | BufferCount = 2, 986 | Scaling = .STRETCH, 987 | SwapEffect = .DISCARD, 988 | } 989 | 990 | dxgi_factory: ^dxgi.IFactory2 991 | ch(s.dxgi_adapter->GetParent(dxgi.IFactory2_UUID, (^rawptr)(&dxgi_factory))) 992 | ch(dxgi_factory->CreateSwapChainForHwnd(s.device, s.window_handle, &swapchain_desc, nil, nil, &s.swapchain)) 993 | ch(s.swapchain->GetBuffer(0, d3d11.ITexture2D_UUID, (^rawptr)(&s.framebuffer))) 994 | ch(s.device->CreateRenderTargetView(s.framebuffer, nil, &s.framebuffer_view)) 995 | 996 | depth_buffer_desc: d3d11.TEXTURE2D_DESC 997 | s.framebuffer->GetDesc(&depth_buffer_desc) 998 | depth_buffer_desc.Format = .D24_UNORM_S8_UINT 999 | depth_buffer_desc.BindFlags = {.DEPTH_STENCIL} 1000 | 1001 | ch(s.device->CreateTexture2D(&depth_buffer_desc, nil, &s.depth_buffer)) 1002 | ch(s.device->CreateDepthStencilView(s.depth_buffer, nil, &s.depth_buffer_view)) 1003 | s.device_context->ClearDepthStencilView(s.depth_buffer_view, {.DEPTH}, 1, 0) 1004 | } 1005 | 1006 | D3D11_Texture :: struct { 1007 | handle: Texture_Handle, 1008 | tex: ^d3d11.ITexture2D, 1009 | view: ^d3d11.IShaderResourceView, 1010 | format: Pixel_Format, 1011 | 1012 | // It may seem strange that we have a sampler here. But samplers are reused if you recreate them 1013 | // with the same options. D3D11 will return the same object. So each time we set the filter 1014 | // mode or the UV wrapping settings, then we just ask D3D11 for the sampler state for those 1015 | // settings. 1016 | // 1017 | // Moreover, in order to make D3D11 behave a bit like GL (or rather, to make them behave more 1018 | // similarly), we require that each texture in the HLSL shaders have a dedicated sampler. 1019 | sampler: ^d3d11.ISamplerState, 1020 | } 1021 | 1022 | D3D11_Render_Target :: struct { 1023 | handle: Render_Target_Handle, 1024 | depth_stencil_texture: ^d3d11.ITexture2D, 1025 | depth_stencil_texture_view: ^d3d11.IDepthStencilView, 1026 | render_target_view: ^d3d11.IRenderTargetView, 1027 | } 1028 | 1029 | dxgi_format_from_pixel_format :: proc(f: Pixel_Format) -> dxgi.FORMAT { 1030 | switch f { 1031 | case .Unknown: return .UNKNOWN 1032 | case .RGBA_32_Float: return .R32G32B32A32_FLOAT 1033 | case .RGB_32_Float: return .R32G32B32_FLOAT 1034 | case .RG_32_Float: return .R32G32_FLOAT 1035 | case .R_32_Float: return .R32_FLOAT 1036 | 1037 | case .RGBA_8_Norm: return .R8G8B8A8_UNORM 1038 | case .RG_8_Norm: return .R8G8_UNORM 1039 | case .R_8_Norm: return .R8_UNORM 1040 | case .R_8_UInt: return .R8_UINT 1041 | } 1042 | 1043 | log.error("Unknown format") 1044 | return .UNKNOWN 1045 | } 1046 | 1047 | // CHeck win errors and print message log if there is any error 1048 | ch :: proc(hr: dxgi.HRESULT, loc := #caller_location) -> dxgi.HRESULT { 1049 | if hr >= 0 { 1050 | return hr 1051 | } 1052 | 1053 | log.errorf("d3d11 error: %0x", u32(hr), location = loc) 1054 | log_messages(loc) 1055 | return hr 1056 | } 1057 | 1058 | log_messages :: proc(loc := #caller_location) { 1059 | iq := s.info_queue 1060 | 1061 | if iq == nil { 1062 | return 1063 | } 1064 | 1065 | n := iq->GetNumStoredMessages() 1066 | longest_msg: d3d11.SIZE_T 1067 | 1068 | for i in 0..=n { 1069 | msglen: d3d11.SIZE_T 1070 | iq->GetMessage(i, nil, &msglen) 1071 | 1072 | if msglen > longest_msg { 1073 | longest_msg = msglen 1074 | } 1075 | } 1076 | 1077 | if longest_msg > 0 { 1078 | msg_raw_ptr, _ := (mem.alloc(int(longest_msg), allocator = frame_allocator)) 1079 | 1080 | for i in 0..=n { 1081 | msglen: d3d11.SIZE_T 1082 | iq->GetMessage(i, nil, &msglen) 1083 | 1084 | if msglen > 0 { 1085 | msg := (^d3d11.MESSAGE)(msg_raw_ptr) 1086 | iq->GetMessage(i, msg, &msglen) 1087 | log.error(msg.pDescription, location = loc) 1088 | } 1089 | } 1090 | } 1091 | 1092 | iq->ClearStoredMessages() 1093 | } 1094 | 1095 | DEFAULT_SHADER_SOURCE :: #load("render_backend_d3d11_default_shader.hlsl") 1096 | 1097 | d3d11_default_shader_vertex_source :: proc() -> []byte { 1098 | s := DEFAULT_SHADER_SOURCE 1099 | return s 1100 | } 1101 | 1102 | d3d11_default_shader_fragment_source :: proc() -> []byte { 1103 | s := DEFAULT_SHADER_SOURCE 1104 | return s 1105 | } --------------------------------------------------------------------------------