├── 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 |
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 | }
--------------------------------------------------------------------------------