├── src
├── puppy.png
├── Roboto-Regular.ttf
└── main.odin
├── README.md
├── crow2d
├── platform_null.odin
├── sound.odin
├── texture.odin
├── font.odin
├── crow.odin
├── io.odin
├── draw.odin
├── platform_js.odin
└── platform_windows.odin
├── index.html
└── odin.js
/src/puppy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gingerBill/gordon2d/HEAD/src/puppy.png
--------------------------------------------------------------------------------
/src/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gingerBill/gordon2d/HEAD/src/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # crow2d
2 |
3 | A work-in-program 2D framework in Odin.
4 |
5 | It's free, open-source, and works on the Web with WASM!
--------------------------------------------------------------------------------
/crow2d/platform_null.odin:
--------------------------------------------------------------------------------
1 | #+build !js
2 | #+build !windows
3 | #+private
4 | package crowd2d
5 |
6 | Platform_Data :: struct {
7 | }
8 |
9 | @(require_results)
10 | platform_init :: proc(ctx: ^Context) -> bool {
11 | return false
12 | }
13 |
14 | platform_fini :: proc(ctx: ^Context) {
15 |
16 | }
17 |
18 | @(require_results)
19 | platform_update :: proc(ctx: ^Context) -> bool {
20 | return false
21 | }
22 |
23 | @(require_results)
24 | platform_draw :: proc(ctx: ^Context) -> bool {
25 | return false
26 | }
27 |
28 | @(require_results)
29 | platform_texture_load_from_img :: proc(ctx: ^Context, img: Image, opts: Texture_Options) -> (tex: Texture, ok: bool) {
30 | return
31 | }
32 |
33 | platform_texture_unload :: proc(ctx: ^Context, tex: Texture) {
34 |
35 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Crow2D
7 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
--------------------------------------------------------------------------------
/crow2d/sound.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | Audio_Source :: struct {
4 | next: ^Audio_Source,
5 | sound: ^Sound,
6 | rate: f32,
7 | position: int,
8 | }
9 |
10 | Sound_Flag :: enum u32 {
11 | Loop,
12 | Fade_Out,
13 | Fade_In,
14 | }
15 |
16 | Sound_Flags :: distinct bit_set[Sound_Flag; u32]
17 |
18 | Sound :: struct {
19 | name: string,
20 | flags: Sound_Flags,
21 | volume: f32,
22 | rate: i32,
23 | channels: i32,
24 | samples: []f32,
25 | samples_per_channel: i32,
26 | sources_count: i32,
27 | }
28 |
29 |
30 | Sound_Error :: enum u32 {
31 | None,
32 | Too_Many_Sources,
33 | No_Source,
34 | }
35 |
36 | sound_play :: proc(ctx: ^Context, sound: ^Sound) -> Sound_Error {
37 | if sound.sources_count > 3 {
38 | return .Too_Many_Sources
39 | }
40 |
41 | source: ^Audio_Source
42 | if ctx.audio_source_pool == nil {
43 | source = new(Audio_Source)
44 | } else {
45 | source = ctx.audio_source_pool
46 | ctx.audio_source_pool = source.next
47 | }
48 |
49 | if source == nil {
50 | return .No_Source
51 | }
52 | sound.sources_count += 1
53 | source^ = {}
54 | source.sound = sound
55 | source.rate = f32(sound.rate) / f32(ctx.sound_sample_rate)
56 | source.next = ctx.audio_source_pool
57 | ctx.audio_source_playback = source
58 | return nil
59 | }
60 |
61 | sound_stop :: proc(ctx: ^Context, sound: ^Sound) -> Sound_Error {
62 | if sound.sources_count == 0 {
63 | return .No_Source
64 | }
65 |
66 | it := &ctx.audio_source_playback
67 | for it^ != nil {
68 | if it^.sound == sound {
69 | next := it^.next
70 | it^.next = ctx.audio_source_pool
71 | ctx.audio_source_pool = it^
72 | sound.sources_count -= 1
73 | it^ = next
74 | } else {
75 | it = &it^.next
76 | }
77 | }
78 |
79 | assert(sound.sources_count == 0)
80 | return nil
81 | }
82 |
83 |
84 |
--------------------------------------------------------------------------------
/crow2d/texture.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | import "core:c"
4 | import "core:slice"
5 |
6 | import stbi "vendor:stb/image"
7 | _ :: stbi
8 |
9 | Image :: struct {
10 | pixels: [][4]u8,
11 | width: i32,
12 | height: i32,
13 | }
14 |
15 |
16 | image_load_from_memory :: proc(data: []byte) -> (img: Image, ok: bool) {
17 | x, y, channels_in_file: c.int
18 | res := stbi.load_from_memory(raw_data(data), c.int(len(data)), &x, &y, &channels_in_file, 4)
19 | if res == nil {
20 | return
21 | }
22 | img.pixels = slice.reinterpret([][4]u8, res[:x*y*channels_in_file])
23 | img.width = x
24 | img.height = y
25 | if channels_in_file != 4 {
26 | image_unload(img)
27 | } else {
28 | ok = true
29 | }
30 | return
31 | }
32 |
33 | image_unload :: proc(img: Image) {
34 | stbi.image_free(raw_data(img.pixels))
35 | }
36 |
37 | Texture_Filter :: enum u8 {
38 | Linear,
39 | Nearest,
40 | }
41 |
42 | Texture_Wrap :: enum u8 {
43 | Clamp_To_Edge,
44 | Repeat,
45 | Mirrored_Repeat,
46 | }
47 |
48 | Texture_Options :: struct {
49 | filter: Texture_Filter,
50 | wrap: [2]Texture_Wrap,
51 | }
52 |
53 | TEXTURE_OPTIONS_DEFAULT :: Texture_Options{}
54 |
55 |
56 | texture_load_default_white :: proc(ctx: ^Context) -> (texture: Texture, ok: bool) {
57 | white_pixel := [1][4]u8{0..<1 = {255, 255, 255, 255}}
58 | img: Image
59 | img.pixels = white_pixel[:]
60 | img.width = 1
61 | img.height = 1
62 | return texture_load_from_image(ctx, img, {
63 | filter = .Nearest,
64 | wrap = {.Clamp_To_Edge, .Clamp_To_Edge},
65 | })
66 | }
67 |
68 |
69 | texture_load_from_memory :: proc(ctx: ^Context, data: []byte, opts := TEXTURE_OPTIONS_DEFAULT) -> (texture: Texture, ok: bool) {
70 | img := image_load_from_memory(data) or_return
71 | defer image_unload(img)
72 |
73 | return texture_load_from_image(ctx, img, opts)
74 | }
75 |
76 | texture_load_from_image :: proc(ctx: ^Context, img: Image, opts := TEXTURE_OPTIONS_DEFAULT) -> (texture: Texture, ok: bool) {
77 | return platform_texture_load_from_img(ctx, img, opts)
78 | }
79 |
80 | texture_unload :: proc(ctx: ^Context, tex: Texture) {
81 | platform_texture_unload(ctx, tex)
82 | }
83 |
--------------------------------------------------------------------------------
/crow2d/font.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | import stbtt "vendor:stb/truetype"
4 |
5 | _ :: stbtt
6 |
7 | Font :: struct {
8 | info: stbtt.fontinfo,
9 |
10 | atlas: Texture,
11 | atlas_width: i32,
12 | atlas_height: i32,
13 |
14 | size: i32,
15 | ascent: i32,
16 | descent: i32,
17 | line_gap: i32,
18 | baseline: i32,
19 |
20 | backed_chars: [96]stbtt.bakedchar,
21 | }
22 |
23 | FONT_ATLAS_SIZE :: 1024
24 |
25 | @(private="file")
26 | temp_font_atlas_data: [FONT_ATLAS_SIZE*FONT_ATLAS_SIZE]u8
27 |
28 | @(private="file")
29 | temp_font_atlas_pixels: [FONT_ATLAS_SIZE*FONT_ATLAS_SIZE][4]u8
30 |
31 |
32 | font_load_from_memory :: proc(ctx: ^Context, data: []byte, size: i32) -> (f: Font, ok: bool) {
33 | size := size
34 | size = max(size, 1)
35 | stbtt.InitFont(&f.info, raw_data(data), 0) or_return
36 | f.size = size
37 |
38 | scale := stbtt.ScaleForPixelHeight(&f.info, f32(f.size))
39 | stbtt.GetFontVMetrics(&f.info, &f.ascent, &f.descent, &f.line_gap)
40 | f.baseline = i32(f32(f.ascent)*scale)
41 |
42 |
43 | f.atlas_width = FONT_ATLAS_SIZE
44 | f.atlas_height = FONT_ATLAS_SIZE
45 | stbtt.BakeFontBitmap(raw_data(data), 0, f32(size), raw_data(temp_font_atlas_data[:]), FONT_ATLAS_SIZE, FONT_ATLAS_SIZE, 32, len(f.backed_chars), raw_data(f.backed_chars[:]))
46 | for b, i in temp_font_atlas_data {
47 | temp_font_atlas_pixels[i] = {255, 255, 255, b}
48 | }
49 |
50 | img := Image{
51 | pixels = temp_font_atlas_pixels[:],
52 | width = FONT_ATLAS_SIZE,
53 | height = FONT_ATLAS_SIZE,
54 | }
55 | f.atlas = texture_load_from_image(ctx, img) or_return
56 |
57 | return
58 | }
59 |
60 | font_unload :: proc(ctx: ^Context, f: Font) {
61 | texture_unload(ctx, f.atlas)
62 | }
63 |
64 |
65 | draw_text :: proc(ctx: ^Context, f: ^Font, text: string, pos: Vec2, color: Color) {
66 | set_texture(ctx, f.atlas)
67 |
68 | next := pos
69 | for c in text {
70 | c := c
71 | switch c {
72 | case '\n':
73 | next.x = pos.x
74 | next.y += f32(f.size)
75 | case '\r', '\t':
76 | c = ' '
77 | }
78 | if 32 <= c && c < 128 {
79 | q: stbtt.aligned_quad
80 | stbtt.GetBakedQuad(&f.backed_chars[0], f.atlas_width, f.atlas_height, i32(c)-32, &next.x, &next.y, &q, true)
81 |
82 |
83 | a := Vertex{pos = {q.x0, q.y0, ctx.curr_z}, uv = {q.s0, q.t0}, col = color}
84 | b := Vertex{pos = {q.x1, q.y0, ctx.curr_z}, uv = {q.s1, q.t0}, col = color}
85 | c := Vertex{pos = {q.x1, q.y1, ctx.curr_z}, uv = {q.s1, q.t1}, col = color}
86 | d := Vertex{pos = {q.x0, q.y1, ctx.curr_z}, uv = {q.s0, q.t1}, col = color}
87 |
88 | append(&ctx.vertices, a, b, c)
89 | append(&ctx.vertices, c, d, a)
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main.odin:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import crow "../crow2d"
4 | import "core:math"
5 | import "core:fmt"
6 | _ :: math
7 | _ :: fmt
8 |
9 | // import "vendor:stb/easy_font"
10 |
11 | ctx0: crow.Context
12 |
13 | puppy: crow.Texture
14 | puppy_data := #load("puppy.png")
15 |
16 | player_pos: crow.Vec2
17 | player_size :: crow.Vec2{64, 64}
18 | player_speed :: 500
19 | ground := f32(0)
20 | y_velocity := f32(0)
21 | jump_height :: -500
22 | gravity :: -1000
23 |
24 | roboto_font: crow.Font
25 |
26 | main :: proc() {
27 | crow.init(&ctx0, "canvas0",
28 | init = proc(ctx: ^crow.Context) -> bool {
29 | puppy = crow.texture_load_from_memory(ctx, puppy_data) or_return
30 |
31 | width := ctx.canvas_size.x
32 | height := ctx.canvas_size.y
33 |
34 | player_pos = {width*0.5, height*0.5 - player_size.y}
35 | ground = player_pos.y
36 |
37 |
38 | // ok: bool
39 | // roboto_font, ok = crow.font_load_from_memory(#load("Roboto-Regular.ttf"), 64)
40 |
41 | return true
42 | },
43 | update = proc(ctx: ^crow.Context, dt: f32) {
44 | width := ctx.canvas_size.x
45 | height := ctx.canvas_size.y
46 | _ = width
47 | _ = height
48 |
49 | crow.draw_rect(ctx, {0, ground+player_size.y}, {width, height-ground-player_size.y}, color={190, 100, 10, 255})
50 | crow.draw_rect(ctx, player_pos, player_size, texture=puppy, color={200, 200, 200, 255})
51 |
52 | // if gamepad := &ctx.io.gamepads[0]; gamepad.connected && gamepad.buttons_pressed != nil {
53 | // fmt.println(gamepad.buttons_pressed)
54 | // }
55 | if gamepad := &ctx.io.gamepads[0]; gamepad.connected {
56 | value := gamepad.axis_values[.Left_X]
57 | if abs(value) < 0.2 {
58 | value = 0
59 | }
60 | player_pos.x += player_speed*dt * value
61 |
62 | if .A in gamepad.buttons_pressed {
63 | if y_velocity == 0 {
64 | y_velocity = jump_height
65 | }
66 | }
67 | }
68 |
69 | // if ctx.io.mouse_pressed != nil {
70 | // fmt.println(ctx.io.mouse_pressed, ctx.io.mouse_pos)
71 | // }
72 |
73 | for key in ctx.io.key_pressed {
74 | fmt.println(key, ctx.io.key_pressed_count_per_frame[key])
75 | }
76 |
77 |
78 |
79 | // ctx.camera.zoom = f32(math.cos(ctx.curr_time) + 2)*2
80 | // ctx.camera.target.x = f32(math.cos(ctx.curr_time))*50
81 | // ctx.camera.target.y = f32(math.sin(ctx.curr_time))*50
82 |
83 | // @static pos := crow.Vec2{200, 200}
84 | // @static col := crow.Colour{0, 255, 0, 255}
85 |
86 | // if .W in ctx.io.key_down {
87 | // player_pos.y -= player_speed*dt
88 | // }
89 | // if .S in ctx.io.key_down {
90 | // player_pos.y += player_speed*dt
91 | // }
92 | if .Space in ctx.io.key_pressed {
93 | if y_velocity == 0 {
94 | y_velocity = jump_height
95 | }
96 | }
97 | if ctx.io.key_down & {.D, .Right} != nil {
98 | if player_pos.x + player_size.x < width {
99 | player_pos.x += player_speed*dt
100 | }
101 | }
102 | if ctx.io.key_down & {.A, .Left} != nil {
103 | if player_pos.x > 0 {
104 | player_pos.x -= player_speed*dt
105 | }
106 | }
107 |
108 | if y_velocity != 0 {
109 | player_pos.y += y_velocity * dt
110 | y_velocity -= gravity * dt
111 | }
112 |
113 | player_pos.x = clamp(player_pos.x, 0, width-player_size.x)
114 | if player_pos.y > ground {
115 | player_pos.y = ground
116 | y_velocity = 0
117 | }
118 | player_pos.y = clamp(player_pos.y, 0, ground)
119 |
120 | crow.draw_text(ctx, &roboto_font, "Hellope!\nWhatever\tthe fuck", {60, 60}, crow.WHITE)
121 |
122 |
123 |
124 | crow.draw_rect(ctx, {500, 500}, {128, 128}, origin={64, 64}, rotation=-2*f32(ctx.curr_time), texture=puppy)
125 |
126 | crow.draw_rect(ctx, {400, 400}, {32, 64}, origin={16, 32}, rotation=f32(ctx.curr_time), color={255, 0, 255, 255})
127 | crow.draw_rect_outline(ctx, {400, 400}, {32, 64}, thickness=10, origin={16, 32}, rotation=f32(ctx.curr_time), color={0, 0, 255, 127})
128 |
129 | crow.draw_spline_catmull_rom(ctx,
130 | {
131 | {10, 10},
132 | {10, 10},
133 | {100, 200},
134 | {50, 300},
135 | {500, 150},
136 | {100, 300},
137 | },
138 | 20,
139 | {255, 0, 0, 255},
140 | )
141 | },
142 | fini = proc(ctx: ^crow.Context) {
143 | crow.texture_unload(ctx, puppy)
144 | })
145 |
146 | crow.start() // only needed on non-javascript platforms
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/crow2d/crow.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | import "core:time"
4 |
5 | Vec2 :: [2]f32
6 | Vec3 :: [3]f32
7 | Vec4 :: [4]f32
8 |
9 | MAX_EVENT_COUNT :: 512
10 |
11 | Color :: distinct [4]u8
12 |
13 | WHITE :: Color{255, 255, 255, 255}
14 | BLACK :: Color{ 0, 0, 0, 255}
15 | BLANK :: Color{ 0, 0, 0, 0}
16 |
17 | LIGHT_GREY :: Color{200, 200, 200, 255}
18 | GREY :: Color{130, 130, 130, 255}
19 | DARK_GREY :: Color{ 80, 80, 80, 255}
20 |
21 | RED :: Color{230, 41, 55, 255}
22 | MAROON :: Color{190, 33, 55, 255}
23 | ORANGE :: Color{255, 161, 0, 255}
24 | YELLOW :: Color{253, 249, 0, 255}
25 | GOLD :: Color{255, 203, 0, 255}
26 | GREEN :: Color{ 0, 228, 48, 255}
27 | LIME :: Color{ 0, 158, 47, 255}
28 | DARK_GREEN :: Color{ 0, 117, 44, 255}
29 | SKY_BLUE :: Color{127, 178, 255, 255}
30 | BLUE :: Color{ 0, 121, 241, 255}
31 | DARK_BLUE :: Color{ 0, 82, 172, 255}
32 | PURPLE :: Color{200, 122, 255, 255}
33 | VIOLET :: Color{135, 60, 190, 255}
34 | DARK_PURPLE :: Color{112, 31, 126, 255}
35 | MAGENTA :: Color{255, 0, 255, 255}
36 | PINK :: Color{255, 109, 194, 255}
37 | BEIGE :: Color{211, 176, 131, 255}
38 | BROWN :: Color{127, 106, 79, 255}
39 | DARK_BROWN :: Color{ 76, 63, 47, 255}
40 |
41 | Camera :: struct {
42 | offset: Vec2,
43 | target: Vec2,
44 | rotation: f32,
45 | zoom: f32,
46 | near: f32,
47 | far: f32,
48 | }
49 | Camera_Default :: Camera{
50 | zoom = 1,
51 | near = -1024,
52 | far = +1024,
53 | }
54 |
55 |
56 | Vertex :: struct {
57 | pos: Vec3, // 3 components to allow for possible depth testing
58 | col: Color,
59 | uv: Vec2,
60 | }
61 |
62 | Clip_Rect :: struct {
63 | pos: Vec2,
64 | size: Vec2,
65 | }
66 |
67 | Draw_Call :: struct {
68 | shader: Shader,
69 | texture: Texture,
70 | depth_test: bool,
71 | clip_rect: Maybe(Clip_Rect),
72 |
73 | offset: int,
74 | length: int,
75 | }
76 |
77 | Draw_State :: struct {
78 | camera: Camera,
79 | vertices: [dynamic]Vertex,
80 | draw_calls: [dynamic]Draw_Call,
81 | }
82 |
83 |
84 | Context :: struct {
85 | canvas_id: string,
86 | canvas_size: Vec2,
87 | pixel_scale: u8,
88 | clear_color: Color,
89 |
90 | prev_time: f64,
91 | curr_time: f64,
92 |
93 | frame_counter: u64,
94 |
95 | io: IO,
96 |
97 | user_data: rawptr,
98 | user_index: int,
99 |
100 | default_shader: Shader,
101 | default_texture: Texture,
102 | vertex_buffer: Buffer,
103 |
104 | curr_z: f32,
105 |
106 | update: Update_Proc,
107 | fini: Fini_Proc,
108 |
109 | using draw_state: Draw_State,
110 |
111 | is_done: bool,
112 |
113 | _next: ^Context,
114 |
115 | platform_data: Platform_Data,
116 |
117 | sound_sample_rate: f32,
118 | audio_source_pool: ^Audio_Source,
119 | audio_source_playback: ^Audio_Source,
120 | }
121 |
122 | Update_Proc :: proc(ctx: ^Context, dt: f32)
123 | Init_Proc :: proc(ctx: ^Context) -> bool
124 | Fini_Proc :: proc(ctx: ^Context)
125 |
126 |
127 | Shader :: struct {
128 | handle: u32,
129 | }
130 | Buffer :: struct {
131 | handle: u32,
132 | }
133 | Texture :: struct {
134 | handle: u32,
135 | width: i32,
136 | height: i32,
137 | }
138 |
139 | HANDLE_INVALID :: ~u32(0)
140 |
141 | SHADER_INVALID :: Shader { handle = HANDLE_INVALID }
142 | BUFFER_INVALID :: Buffer { handle = HANDLE_INVALID }
143 | TEXTURE_INVALID :: Texture{ handle = HANDLE_INVALID }
144 |
145 |
146 | @(private)
147 | global_context_list: ^Context
148 |
149 | init :: proc(ctx: ^Context, canvas_id: string, init: Init_Proc, update: Update_Proc, fini: Fini_Proc = nil, pixel_scale := 1) -> bool {
150 | ctx.canvas_id = canvas_id
151 | ctx.update = update
152 | ctx.fini = fini
153 | ctx.pixel_scale = u8(clamp(pixel_scale, 1, 255))
154 | ctx.camera = Camera_Default
155 | ctx.clear_color = SKY_BLUE
156 | ctx.curr_z = 0
157 | ctx.sound_sample_rate = 48000
158 |
159 | reserve(&ctx.vertices, 1<<20)
160 | reserve(&ctx.draw_calls, 1<<12)
161 |
162 | platform_init(ctx) or_return
163 |
164 | ctx._next = global_context_list
165 | global_context_list = ctx
166 |
167 | if !init(ctx) {
168 | fini(ctx)
169 | return false
170 | }
171 |
172 | return true
173 | }
174 |
175 | stop :: proc(ctx: ^Context) {
176 | ctx.is_done = true
177 | }
178 |
179 | // Only needed for non-JS platforms
180 | start :: proc() {
181 | tick := time.tick_now()
182 | for ODIN_OS != .JS && global_context_list != nil {
183 | dt := time.duration_seconds(time.tick_lap_time(&tick))
184 | if !step(dt) {
185 | break
186 | }
187 | }
188 | }
189 |
190 | @(private)
191 | fini :: proc(ctx: ^Context) {
192 | if ctx == nil {
193 | return
194 | }
195 | if ctx.fini != nil {
196 | ctx->fini()
197 | }
198 |
199 | platform_fini(ctx)
200 |
201 | delete(ctx.vertices)
202 | delete(ctx.draw_calls)
203 | }
204 |
205 |
206 | @(export)
207 | step :: proc(delta_time: f64) -> bool {
208 | free_all(context.temp_allocator)
209 |
210 | for ctx := global_context_list; ctx != nil; /**/ {
211 | defer ctx = ctx._next
212 |
213 | curr_time := ctx.curr_time + delta_time
214 | dt := curr_time - ctx.curr_time
215 | ctx.prev_time = ctx.curr_time
216 | ctx.curr_time = curr_time
217 | ctx.frame_counter += 1
218 | ctx.curr_z = 0
219 |
220 | if ctx.is_done {
221 | p := &global_context_list
222 | for p^ != ctx {
223 | p = &p^._next
224 | }
225 | p^ = ctx._next
226 | fini(ctx)
227 | continue
228 | }
229 |
230 | platform_update(ctx) or_continue
231 |
232 | io_init(ctx)
233 | defer io_fini(ctx)
234 |
235 | ctx.update(ctx, f32(dt))
236 |
237 | _ = draw_all(ctx)
238 | }
239 | return global_context_list != nil
240 | }
241 |
242 |
243 | @(private)
244 | draw_all :: proc(ctx: ^Context) -> bool {
245 | if len(ctx.draw_calls) > 0 {
246 | last := &ctx.draw_calls[len(ctx.draw_calls)-1]
247 | last.length = len(ctx.vertices)-last.offset
248 | }
249 |
250 | ok := platform_draw(ctx)
251 | clear(&ctx.vertices)
252 | clear(&ctx.draw_calls)
253 | return ok
254 | }
255 |
--------------------------------------------------------------------------------
/crow2d/io.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | IO :: struct {
4 | mouse_pos: [2]i32,
5 | mouse_last_pos: [2]i32,
6 | mouse_pressed_pos: [2]i32,
7 | mouse_delta: [2]i32,
8 |
9 | mouse_down: Mouse_Button_Set,
10 | mouse_pressed: Mouse_Button_Set,
11 | mouse_released: Mouse_Button_Set,
12 | internal_mouse_was_down: Mouse_Button_Set,
13 |
14 | key_down: Key_Set,
15 |
16 | key_pressed: Key_Set,
17 | key_released: Key_Set,
18 | key_repeat: Key_Set,
19 |
20 | modifiers: Modifier_Key_Set,
21 | pressed_key_stroke: Key_Stroke,
22 |
23 | key_pressed_count_per_frame: [Key]u8,
24 |
25 | last_key_press_time: f64,
26 |
27 | scroll_delta: [2]i32,
28 |
29 | gamepads: [MAX_GAMEPADS]Gamepad,
30 | connected_gamepad_count: u32,
31 |
32 | click_count: i32,
33 |
34 | full_reset: bool,
35 | }
36 |
37 |
38 | Mouse_Button :: enum u16 {
39 | Left,
40 | Right,
41 | Middle,
42 | }
43 |
44 | Modifier_Key :: enum u32 {
45 | Ctrl,
46 | Shift,
47 | Alt,
48 | }
49 |
50 | Key :: enum u16 {
51 | Invalid,
52 |
53 | Left_Ctrl, Left_Shift, Left_Alt,
54 | Right_Ctrl, Right_Shift, Right_Alt,
55 |
56 | A, B, C, D, E, F, G, H,
57 | I, J, K, L, M, N, O, P,
58 | Q, R, S, T, U, V, W, X,
59 | Y, Z,
60 |
61 | Key_0, Key_1, Key_2, Key_3, Key_4,
62 | Key_5, Key_6, Key_7, Key_8, Key_9,
63 |
64 | Numpad_0, Numpad_1, Numpad_2, Numpad_3, Numpad_4,
65 | Numpad_5, Numpad_6, Numpad_7, Numpad_8, Numpad_9,
66 | Numpad_Divide, Numpad_Multiply, Numpad_Subtract,
67 | Numpad_Add, Numpad_Enter, Numpad_Decimal,
68 |
69 | Escape,
70 | Return,
71 | Tab,
72 | Backspace,
73 | Space,
74 | Delete,
75 | Insert,
76 |
77 | Apostrophe,
78 | Comma,
79 | Minus,
80 | Period,
81 | Slash,
82 | Semicolon,
83 | Equal,
84 | Backslash,
85 | Bracket_Left,
86 | Bracket_Right,
87 | Grave_Accent,
88 | Home,
89 | End,
90 | Page_Up,
91 | Page_Down,
92 |
93 |
94 | Up,
95 | Down,
96 | Left,
97 | Right,
98 | }
99 |
100 | Key_Stroke :: struct {
101 | modifiers: Modifier_Key_Set,
102 | key: Key,
103 | }
104 |
105 |
106 | MAX_GAMEPADS :: 8
107 |
108 | Gamepad_Axis :: enum u16 {
109 | Left_X,
110 | Left_Y,
111 | Right_X,
112 | Right_Y,
113 | Left_Trigger,
114 | Right_Trigger,
115 | }
116 |
117 |
118 | Gamepad_Button :: enum u32 {
119 | A,
120 | B,
121 | X,
122 | Y,
123 | Left_Shoulder,
124 | Right_Shoulder,
125 | Left_Trigger,
126 | Right_Trigger,
127 | Back,
128 | Start,
129 | Left_Stick,
130 | Right_Stick,
131 | Dpad_Up,
132 | Dpad_Down,
133 | Dpad_Left,
134 | Dpad_Right,
135 | Guide,
136 |
137 | Misc1, // Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button
138 | Paddle1, // Xbox Elite paddle P1
139 | Paddle2, // Xbox Elite paddle P3
140 | Paddle3, // Xbox Elite paddle P2
141 | Paddle4, // Xbox Elite paddle P4
142 | Touchpad, // PS4/PS5 touchpad button
143 | }
144 |
145 | Gamepad_Button_Set :: distinct bit_set[Gamepad_Button; u32]
146 |
147 | Gamepad :: struct {
148 | connected: bool,
149 | index: i32,
150 |
151 | axis_values: [Gamepad_Axis]f32,
152 | button_values: [Gamepad_Button]f32,
153 | buttons_down: Gamepad_Button_Set,
154 | buttons_pressed: Gamepad_Button_Set,
155 | buttons_released: Gamepad_Button_Set,
156 | internal_buttons_was_down: Gamepad_Button_Set,
157 |
158 | }
159 |
160 | Mouse_Button_Set :: distinct bit_set[Mouse_Button; u16]
161 | Key_Set :: distinct bit_set[Key; u128]
162 | Modifier_Key_Set :: distinct bit_set[Modifier_Key; u32]
163 |
164 | MODIFIER_KEYS :: Key_Set{
165 | .Left_Ctrl, .Left_Shift, .Left_Alt,
166 | .Right_Ctrl, .Right_Shift, .Right_Alt,
167 | }
168 |
169 |
170 | key_strings := [Key]string{
171 | .Invalid = "invalid",
172 |
173 | .Left_Ctrl = "lctrl",
174 | .Left_Shift = "lshift",
175 | .Left_Alt = "lalt",
176 | .Right_Ctrl = "rctrl",
177 | .Right_Shift = "rshift",
178 | .Right_Alt = "ralt",
179 |
180 | .Escape = "escape",
181 | .Return = "return",
182 | .Tab = "tab",
183 | .Backspace = "backspace",
184 | .Space = "space",
185 | .Delete = "delete",
186 | .Insert = "insert",
187 |
188 | .Apostrophe = "'",
189 | .Comma = ",",
190 | .Minus = "-",
191 | .Period = ".",
192 | .Slash = "/",
193 | .Semicolon = ";",
194 | .Equal = "=",
195 | .Backslash = `\`,
196 | .Bracket_Left = "[",
197 | .Bracket_Right = "]",
198 | .Grave_Accent = "`",
199 | .Home = "home",
200 | .End = "end",
201 | .Page_Up = "page_up",
202 | .Page_Down = "page_down",
203 |
204 |
205 | .A = "a", .B = "b", .C = "c", .D = "d", .E = "e", .F = "f", .G = "g", .H = "h",
206 | .I = "i", .J = "j", .K = "k", .L = "l", .M = "m", .N = "n", .O = "o", .P = "p",
207 | .Q = "q", .R = "r", .S = "s", .T = "t", .U = "u", .V = "v", .W = "w", .X = "x",
208 | .Y = "y", .Z = "z",
209 |
210 | .Key_0 = "0", .Key_1 = "1", .Key_2 = "2", .Key_3 = "3", .Key_4 = "4",
211 | .Key_5 = "5", .Key_6 = "6", .Key_7 = "7", .Key_8 = "8", .Key_9 = "9",
212 |
213 | .Numpad_0 = "numpad_0", .Numpad_1 = "numpad_1", .Numpad_2 = "numpad_2", .Numpad_3 = "numpad_3", .Numpad_4 = "numpad_4",
214 | .Numpad_5 = "numpad_5", .Numpad_6 = "numpad_6", .Numpad_7 = "numpad_7", .Numpad_8 = "numpad_8", .Numpad_9 = "numpad_9",
215 | .Numpad_Divide = "numpad_divide", .Numpad_Multiply = "numpad_multiply", .Numpad_Subtract = "numpad_subtract",
216 | .Numpad_Add = "numpad_add", .Numpad_Enter = "numpad_enter", .Numpad_Decimal = "numpad_decimal",
217 |
218 | .Up = "up",
219 | .Down = "down",
220 | .Left = "left",
221 | .Right = "right",
222 | }
223 |
224 |
225 |
226 | io_init :: proc(ctx: ^Context) {
227 | ctx.io.key_pressed -= ctx.io.key_released
228 |
229 | ctx.io.key_down += ctx.io.key_pressed
230 | ctx.io.key_down -= ctx.io.key_released
231 |
232 | ctx.io.mouse_delta = ctx.io.mouse_pos - ctx.io.mouse_last_pos
233 |
234 | ctx.io.mouse_pressed = ctx.io.mouse_down - ctx.io.internal_mouse_was_down
235 | ctx.io.mouse_released = ctx.io.internal_mouse_was_down - ctx.io.mouse_down
236 |
237 | ctx.io.modifiers = nil
238 | for mod in MODIFIER_KEYS {
239 | if mod in ctx.io.key_down {
240 | #partial switch mod {
241 | case .Left_Ctrl, .Right_Ctrl: ctx.io.modifiers += {.Ctrl}
242 | case .Left_Shift, .Right_Shift: ctx.io.modifiers += {.Shift}
243 | case .Left_Alt, .Right_Alt: ctx.io.modifiers += {.Alt}
244 | }
245 | }
246 | }
247 |
248 | for &gamepad in ctx.io.gamepads {
249 | if gamepad.connected {
250 | gamepad.buttons_pressed = gamepad.buttons_down - gamepad.internal_buttons_was_down
251 | gamepad.buttons_released = gamepad.internal_buttons_was_down - gamepad.buttons_down
252 | }
253 | }
254 |
255 | }
256 | io_fini :: proc(ctx: ^Context) {
257 | ctx.io.mouse_delta = { 0, 0 }
258 | ctx.io.scroll_delta = { 0, 0 }
259 | ctx.io.mouse_last_pos = ctx.io.mouse_pos
260 |
261 | ctx.io.internal_mouse_was_down = ctx.io.mouse_down
262 | ctx.io.mouse_down = nil
263 |
264 | ctx.io.key_pressed = nil
265 | ctx.io.key_released = nil
266 | ctx.io.key_repeat = nil
267 |
268 | ctx.io.pressed_key_stroke = {}
269 |
270 | for &gamepad in ctx.io.gamepads {
271 | if gamepad.connected {
272 | gamepad.internal_buttons_was_down = gamepad.buttons_down
273 | gamepad.buttons_down = nil
274 | }
275 | }
276 |
277 | ctx.io.key_pressed_count_per_frame = {}
278 |
279 | if ctx.io.full_reset {
280 | ctx.io.key_released = ctx.io.key_down
281 | ctx.io.mouse_released = ctx.io.mouse_down
282 | ctx.io.key_down = nil
283 | ctx.io.key_pressed = nil
284 | ctx.io.key_repeat = nil
285 | ctx.io.mouse_down = nil
286 | ctx.io.mouse_pressed = nil
287 | ctx.io.modifiers = nil
288 |
289 | for &gamepad in ctx.io.gamepads {
290 | if gamepad.connected {
291 | gamepad.buttons_released = gamepad.buttons_down
292 | gamepad.buttons_down = nil
293 | gamepad.buttons_pressed = nil
294 | }
295 | }
296 |
297 | ctx.io.full_reset = false
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/crow2d/draw.odin:
--------------------------------------------------------------------------------
1 | package crowd2d
2 |
3 | import "core:math"
4 | import "core:math/linalg"
5 |
6 | @(require_results)
7 | default_draw_call :: proc(ctx: ^Context) -> Draw_Call {
8 | return Draw_Call{
9 | shader = ctx.default_shader,
10 | texture = ctx.default_texture,
11 |
12 | depth_test = false,
13 |
14 | offset = len(ctx.vertices),
15 | length = 0,
16 | }
17 | }
18 |
19 | set_shader :: proc(ctx: ^Context, shader: Shader) -> (prev: Shader) {
20 | prev = ctx.default_shader
21 | dc := default_draw_call(ctx)
22 | if len(ctx.draw_calls) != 0 {
23 | last := &ctx.draw_calls[len(ctx.draw_calls)-1]
24 | prev = last.shader
25 | if last.shader == shader {
26 | return
27 | }
28 | last.length = len(ctx.vertices)-last.offset
29 | dc = last^
30 | }
31 | dc.shader = shader
32 | dc.offset = len(ctx.vertices)
33 | append(&ctx.draw_calls, dc)
34 | return
35 | }
36 |
37 | set_texture :: proc(ctx: ^Context, texture: Texture) -> (prev: Texture) {
38 | texture := texture
39 | if texture.handle == HANDLE_INVALID {
40 | texture = ctx.default_texture
41 | }
42 |
43 | prev = ctx.default_texture
44 | dc := default_draw_call(ctx)
45 | if len(ctx.draw_calls) != 0 {
46 | last := &ctx.draw_calls[len(ctx.draw_calls)-1]
47 | prev = last.texture
48 | if last.texture == texture {
49 | return
50 | }
51 | last.length = len(ctx.vertices)-last.offset
52 | dc = last^
53 | }
54 | dc.texture = texture
55 | dc.offset = len(ctx.vertices)
56 | append(&ctx.draw_calls, dc)
57 | return
58 | }
59 |
60 | set_depth_test :: proc(ctx: ^Context, depth_test: bool) -> (prev: bool) {
61 | prev = false
62 | dc := default_draw_call(ctx)
63 | if len(ctx.draw_calls) != 0 {
64 | last := &ctx.draw_calls[len(ctx.draw_calls)-1]
65 | prev = last.depth_test
66 | if last.depth_test == depth_test {
67 | return
68 | }
69 | last.length = len(ctx.vertices)-last.offset
70 | dc = last^
71 | }
72 | dc.depth_test = depth_test
73 | dc.offset = len(ctx.vertices)
74 | append(&ctx.draw_calls, dc)
75 | return
76 | }
77 |
78 | set_clip_rect :: proc(ctx: ^Context, clip_rect: Maybe(Clip_Rect)) -> (prev: Maybe(Clip_Rect)) {
79 | prev = nil
80 | dc := default_draw_call(ctx)
81 | if len(ctx.draw_calls) != 0 {
82 | last := &ctx.draw_calls[len(ctx.draw_calls)-1]
83 | prev = last.clip_rect
84 | if last.clip_rect == clip_rect {
85 | return
86 | }
87 | last.length = len(ctx.vertices)-last.offset
88 | dc = last^
89 | }
90 | dc.clip_rect = clip_rect
91 | dc.offset = len(ctx.vertices)
92 | append(&ctx.draw_calls, dc)
93 | return
94 | }
95 |
96 | @(private)
97 | rotate_vectors :: proc(ctx: ^Context, offset: int, pos, origin: Vec2, rotation: f32) {
98 | s, c := math.sincos(rotation)
99 | for &v in ctx.vertices[offset:] {
100 | p := v.pos.xy - pos - origin
101 | p = {c*p.x - s*p.y, s*p.x + c*p.y}
102 | p.xy += pos
103 | v.pos.xy = p
104 | }
105 | }
106 |
107 |
108 | draw_rect :: proc(
109 | ctx: ^Context, pos: Vec2, size: Vec2,
110 | origin := Vec2{0, 0},
111 | rotation := f32(0),
112 | texture := TEXTURE_INVALID,
113 | uv0 := Vec2{0, 0},
114 | uv1 := Vec2{1, 1},
115 | color := WHITE,
116 | ) {
117 | set_texture(ctx, texture)
118 |
119 | offset := len(ctx.vertices)
120 |
121 | a := pos
122 | b := pos + {size.x, 0}
123 | c := pos + {size.x, size.y}
124 | d := pos + {0, size.y}
125 |
126 | z := ctx.curr_z
127 |
128 | append(&ctx.vertices, Vertex{pos = {a.x, a.y, z}, col = color, uv = {uv0.x, uv0.y}})
129 | append(&ctx.vertices, Vertex{pos = {b.x, b.y, z}, col = color, uv = {uv1.x, uv0.y}})
130 | append(&ctx.vertices, Vertex{pos = {c.x, c.y, z}, col = color, uv = {uv1.x, uv1.y}})
131 |
132 | append(&ctx.vertices, Vertex{pos = {c.x, c.y, z}, col = color, uv = {uv1.x, uv1.y}})
133 | append(&ctx.vertices, Vertex{pos = {d.x, d.y, z}, col = color, uv = {uv0.x, uv1.y}})
134 | append(&ctx.vertices, Vertex{pos = {a.x, a.y, z}, col = color, uv = {uv0.x, uv0.y}})
135 | rotate_vectors(ctx, offset, pos, origin, rotation)
136 | }
137 |
138 | draw_rect_outline :: proc(
139 | ctx: ^Context, pos: Vec2, size: Vec2, thickness: f32,
140 | origin := Vec2{0, 0},
141 | rotation := f32(0),
142 | color := WHITE,
143 | ) {
144 | offset := len(ctx.vertices)
145 |
146 | draw_rect(ctx, pos + {0, -thickness}, {size.x+thickness, thickness}, color=color)
147 | draw_rect(ctx, pos + {size.x, 0}, {thickness, size.y+thickness}, color=color)
148 |
149 | draw_rect(ctx, pos + {-thickness, size.y}, {size.x+thickness, thickness}, color=color)
150 | draw_rect(ctx, pos + {-thickness, -thickness}, {thickness, size.y+thickness}, color=color)
151 |
152 | for &v in ctx.vertices[offset:] {
153 | v.uv = {0, 0}
154 | }
155 | rotate_vectors(ctx, offset, pos, origin, rotation)
156 | }
157 |
158 |
159 |
160 | draw_quad :: proc(ctx: ^Context, verts: [4]Vec2, color: Color, tex := TEXTURE_INVALID, uvs := [4]Vec2{}) {
161 | set_texture(ctx, tex)
162 |
163 | z := ctx.curr_z
164 |
165 | a := Vertex{pos = {verts[0].x, verts[0].y, z}, uv = uvs[0], col = color}
166 | b := Vertex{pos = {verts[1].x, verts[1].y, z}, uv = uvs[1], col = color}
167 | c := Vertex{pos = {verts[2].x, verts[2].y, z}, uv = uvs[2], col = color}
168 | d := Vertex{pos = {verts[3].x, verts[3].y, z}, uv = uvs[3], col = color}
169 |
170 | append(&ctx.vertices, a, b, c)
171 | append(&ctx.vertices, c, d, a)
172 | }
173 |
174 |
175 | draw_convex_polygon :: proc(ctx: ^Context, vertices: []Vertex, tex := TEXTURE_INVALID) {
176 | set_texture(ctx, tex)
177 |
178 | for i in 0.. 0 {
460 | vertices[0].x = curr_point.x + dy*size
461 | vertices[0].y = curr_point.y - dx*size
462 | vertices[1].x = curr_point.x - dy*size
463 | vertices[1].y = curr_point.y + dx*size
464 | }
465 |
466 | for j in 1..=SPLINE_SEGMENT_DIVISIONS {
467 | t := f32(j)/f32(SPLINE_SEGMENT_DIVISIONS)
468 |
469 | next_point.x = a[3] + t*(a[2] + t*(a[1] + t*a[0]))
470 | next_point.y = b[3] + t*(b[2] + t*(b[1] + t*b[0]))
471 |
472 | dy = next_point.y - curr_point.y
473 | dx = next_point.x - curr_point.x
474 | size = 0.5*thickness/math.sqrt(dx*dx+dy*dy)
475 |
476 | if (i == 0) && (j == 1) {
477 | vertices[0].x = curr_point.x + dy*size
478 | vertices[0].y = curr_point.y - dx*size
479 | vertices[1].x = curr_point.x - dy*size
480 | vertices[1].y = curr_point.y + dx*size
481 | }
482 |
483 | vertices[2*j + 1].x = next_point.x - dy*size
484 | vertices[2*j + 1].y = next_point.y + dx*size
485 | vertices[2*j].x = next_point.x + dy*size
486 | vertices[2*j].y = next_point.y - dx*size
487 |
488 | curr_point = next_point
489 | }
490 |
491 | draw_triangle_strip(ctx, vertices[:], color)
492 | }
493 |
494 | draw_circle(ctx, curr_point, thickness*0.5, color)
495 | }
496 |
497 | draw_spline_catmull_rom :: proc(ctx: ^Context, points: []Vec2, thickness: f32, color: Color) {
498 | if len(points) < 4 {
499 | return
500 | }
501 |
502 | dy := f32(0)
503 | dx := f32(0)
504 | size := f32(0)
505 |
506 | curr_point := points[1]
507 | next_point: Vec2
508 | vertices: [2*SPLINE_SEGMENT_DIVISIONS + 2]Vec2
509 |
510 | draw_circle(ctx, curr_point, thickness*0.5, color)
511 |
512 | for i in 0.. 0 {
519 | vertices[0].x = curr_point.x + dy*size
520 | vertices[0].y = curr_point.y - dx*size
521 | vertices[1].x = curr_point.x - dy*size
522 | vertices[1].y = curr_point.y + dx*size
523 | }
524 |
525 | for j in 1..=SPLINE_SEGMENT_DIVISIONS {
526 | t := f32(j)/f32(SPLINE_SEGMENT_DIVISIONS)
527 |
528 | q0 := (-1.0*t*t*t) + (2.0*t*t) + (-1.0*t)
529 | q1 := (3.0*t*t*t) + (-5.0*t*t) + 2.0
530 | q2 := (-3.0*t*t*t) + (4.0*t*t) + t
531 | q3 := t*t*t - t*t
532 |
533 | next_point.x = 0.5*((p0.x*q0) + (p1.x*q1) + (p2.x*q2) + (p3.x*q3))
534 | next_point.y = 0.5*((p0.y*q0) + (p1.y*q1) + (p2.y*q2) + (p3.y*q3))
535 |
536 | dy = next_point.y - curr_point.y
537 | dx = next_point.x - curr_point.x
538 | size = (0.5*thickness)/math.sqrt(dx*dx + dy*dy)
539 |
540 | if (i == 0) && (j == 1) {
541 | vertices[0].x = curr_point.x + dy*size
542 | vertices[0].y = curr_point.y - dx*size
543 | vertices[1].x = curr_point.x - dy*size
544 | vertices[1].y = curr_point.y + dx*size
545 | }
546 |
547 | vertices[2*j + 1].x = next_point.x - dy*size
548 | vertices[2*j + 1].y = next_point.y + dx*size
549 | vertices[2*j].x = next_point.x + dy*size
550 | vertices[2*j].y = next_point.y - dx*size
551 |
552 | curr_point = next_point
553 | }
554 |
555 | draw_triangle_strip(ctx, vertices[:], color)
556 | }
557 |
558 | draw_circle(ctx, curr_point, thickness*0.5, color)
559 | }
--------------------------------------------------------------------------------
/crow2d/platform_js.odin:
--------------------------------------------------------------------------------
1 | #+private
2 | package crowd2d
3 |
4 | import js "core:sys/wasm/js"
5 | import gl "vendor:wasm/WebGL"
6 |
7 | import glm "core:math/linalg/glsl"
8 |
9 | Platform_Data :: struct {
10 | }
11 |
12 | @(require_results)
13 | platform_init :: proc(ctx: ^Context) -> bool {
14 | gl.CreateCurrentContextById(ctx.canvas_id, gl.DEFAULT_CONTEXT_ATTRIBUTES) or_return
15 | assert(gl.IsWebGL2Supported(), "WebGL 2 must be supported")
16 |
17 | gl.SetCurrentContextById(ctx.canvas_id) or_return
18 | ctx.default_shader.handle = u32(gl.CreateProgramFromStrings({shader_vert}, {shader_frag}) or_return)
19 |
20 | ctx.vertex_buffer.handle = u32(gl.CreateBuffer())
21 | gl.BindBuffer(gl.ARRAY_BUFFER, gl.Buffer(ctx.vertex_buffer.handle))
22 | gl.BufferData(gl.ARRAY_BUFFER, len(ctx.vertices)*size_of(ctx.vertices[0]), nil, gl.DYNAMIC_DRAW)
23 |
24 | ctx.default_texture = texture_load_default_white(ctx) or_return
25 |
26 | for kind in events_to_handle {
27 | if window_wide_events[kind] {
28 | js.add_window_event_listener(kind, ctx, platform_event_callback, true)
29 | } else {
30 | js.add_event_listener(ctx.canvas_id, kind, ctx, platform_event_callback, true)
31 | }
32 | }
33 |
34 | _ = platform_sound_init(ctx)
35 |
36 | platform_update(ctx) or_return
37 | return true
38 | }
39 |
40 | platform_fini :: proc(ctx: ^Context) {
41 | for kind in events_to_handle {
42 | if window_wide_events[kind] {
43 | js.remove_window_event_listener(kind, ctx, platform_event_callback)
44 | } else {
45 | js.remove_event_listener(ctx.canvas_id, kind, ctx, platform_event_callback)
46 | }
47 | }
48 |
49 | texture_unload(ctx, ctx.default_texture)
50 |
51 | gl.DeleteBuffer(gl.Buffer(ctx.vertex_buffer.handle))
52 | gl.DeleteProgram(gl.Program(ctx.default_shader.handle))
53 |
54 | _ = platform_sound_fini(ctx)
55 | }
56 |
57 | @(require_results)
58 | platform_update :: proc(ctx: ^Context) -> bool {
59 | {
60 | client_width := i32(js.get_element_key_f64(ctx.canvas_id, "clientWidth"))
61 | client_height := i32(js.get_element_key_f64(ctx.canvas_id, "clientHeight"))
62 | client_width /= max(i32(ctx.pixel_scale), 1)
63 | client_height /= max(i32(ctx.pixel_scale), 1)
64 | client_width = max(client_width, 1)
65 | client_height = max(client_height, 1)
66 |
67 | width := max(i32(js.get_element_key_f64(ctx.canvas_id, "width")), 1)
68 | height := max(i32(js.get_element_key_f64(ctx.canvas_id, "height")), 1)
69 |
70 | if client_width != width {
71 | js.set_element_key_f64(ctx.canvas_id, "width", f64(client_width))
72 | }
73 | if client_height != height {
74 | js.set_element_key_f64(ctx.canvas_id, "height", f64(client_height))
75 | }
76 |
77 | ctx.canvas_size.x = f32(client_width)
78 | ctx.canvas_size.y = f32(client_height)
79 | }
80 |
81 | for &gamepad in ctx.io.gamepads {
82 | if state: js.Gamepad_State; gamepad.connected && js.get_gamepad_state(int(gamepad.index), &state) {
83 | for button, i in state.buttons {
84 | if i < len(Gamepad_Button) {
85 | // TODO(bill): Don't rely on just Xbox360 layout
86 | b := Gamepad_Button(i)
87 | if button.pressed {
88 | gamepad.buttons_down += {b}
89 | } else {
90 | gamepad.buttons_down -= {b}
91 | }
92 | gamepad.button_values[b] = f32(button.value)
93 | }
94 | }
95 | for axis, i in state.axes {
96 | if i < len(Gamepad_Axis) {
97 | a := Gamepad_Axis(i)
98 | gamepad.axis_values[a] = f32(axis)
99 |
100 | }
101 | }
102 |
103 | }
104 | }
105 |
106 | _ = platform_sound_step(ctx)
107 |
108 | gl.SetCurrentContextById(ctx.canvas_id) or_return
109 |
110 | return true
111 | }
112 |
113 | @(require_results)
114 | platform_draw :: proc(ctx: ^Context) -> bool {
115 | enable_shader_state :: proc(ctx: ^Context, shader: gl.Program, camera: Camera, width, height: i32) -> (mvp: glm.mat4) {
116 | gl.UseProgram(shader)
117 |
118 | a_pos := gl.GetAttribLocation(shader, "a_pos")
119 | gl.EnableVertexAttribArray(a_pos)
120 | gl.VertexAttribPointer(a_pos, 3, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, pos))
121 |
122 | a_col := gl.GetAttribLocation(shader, "a_col")
123 | gl.EnableVertexAttribArray(a_col)
124 | gl.VertexAttribPointer(a_col, 4, gl.UNSIGNED_BYTE, true, size_of(Vertex), offset_of(Vertex, col))
125 |
126 | a_uv := gl.GetAttribLocation(shader, "a_uv")
127 | gl.EnableVertexAttribArray(a_uv)
128 | gl.VertexAttribPointer(a_uv, 2, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, uv))
129 |
130 | {
131 | proj := glm.mat4Ortho3d(0, f32(width), f32(height), 0, camera.near, camera.far)
132 |
133 | origin := glm.mat4Translate({-camera.target.x, -camera.target.y, 0})
134 | rotation := glm.mat4Rotate({0, 0, 1}, camera.rotation)
135 | scale := glm.mat4Scale({camera.zoom, camera.zoom, 1})
136 | translation := glm.mat4Translate({camera.offset.x, camera.offset.y, 0})
137 |
138 | view := origin * scale * rotation * translation
139 |
140 | mvp = proj * view
141 |
142 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_camera"), mvp)
143 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_view"), view)
144 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_projection"), proj)
145 | }
146 | gl.Uniform2f(gl.GetUniformLocation(shader, "u_screen_size"), f32(width), f32(height))
147 | gl.Uniform2f(gl.GetUniformLocation(shader, "u_mouse_pos"), f32(ctx.io.mouse_pos.x), f32(ctx.io.mouse_pos.y))
148 |
149 |
150 | gl.Uniform1i(gl.GetUniformLocation(gl.Program(shader), "u_texture"), 0)
151 | return
152 | }
153 |
154 | gl.SetCurrentContextById(ctx.canvas_id) or_return
155 |
156 | gl.BindBuffer(gl.ARRAY_BUFFER, gl.Buffer(ctx.vertex_buffer.handle))
157 | gl.BufferData(gl.ARRAY_BUFFER, len(ctx.vertices)*size_of(ctx.vertices[0]), raw_data(ctx.vertices), gl.DYNAMIC_DRAW)
158 |
159 |
160 | width, height := gl.DrawingBufferWidth(), gl.DrawingBufferHeight()
161 |
162 | gl.Viewport(0, 0, width, height)
163 | gl.ClearColor(f32(ctx.clear_color.r)/255, f32(ctx.clear_color.g)/255, f32(ctx.clear_color.b)/255, f32(ctx.clear_color.a)/255)
164 | gl.Disable(gl.DEPTH_TEST)
165 | gl.Enable(gl.BLEND)
166 | gl.Enable(gl.CULL_FACE)
167 | gl.CullFace(gl.BACK)
168 | gl.FrontFace(gl.CW)
169 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
170 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
171 |
172 |
173 | prev_draw_call := Draw_Call{}
174 | prev_draw_call.shader = SHADER_INVALID
175 | prev_draw_call.texture = TEXTURE_INVALID
176 |
177 | mvp: glm.mat4
178 |
179 | draw_call_loop: for dc in ctx.draw_calls {
180 | defer prev_draw_call = dc
181 |
182 | if prev_draw_call.depth_test != dc.depth_test {
183 | if dc.depth_test {
184 | gl.Enable(gl.DEPTH_TEST)
185 | } else {
186 | gl.Disable(gl.DEPTH_TEST)
187 | }
188 | }
189 |
190 | if prev_draw_call.texture != dc.texture {
191 | gl.ActiveTexture(gl.TEXTURE0)
192 | gl.BindTexture(gl.TEXTURE_2D, gl.Texture(dc.texture.handle))
193 | }
194 |
195 | if prev_draw_call.shader != dc.shader {
196 | mvp = enable_shader_state(ctx, gl.Program(dc.shader.handle), ctx.camera, width, height)
197 | }
198 |
199 | if prev_draw_call.clip_rect != dc.clip_rect {
200 | if r, ok := dc.clip_rect.?; ok {
201 | gl.Enable(gl.SCISSOR_TEST)
202 | a := r.pos
203 | b := r.pos + r.size
204 |
205 | a.x = clamp(a.x, 0, ctx.canvas_size.x-1)
206 | a.y = clamp(a.y, 0, ctx.canvas_size.y-1)
207 |
208 | b.x = clamp(b.x, 0, ctx.canvas_size.x-1)
209 | b.y = clamp(b.y, 0, ctx.canvas_size.y-1)
210 |
211 | w := i32(b.x-a.x)
212 | h := i32(b.y-a.y)
213 |
214 | if w <= 0 || h <= 0 {
215 | continue draw_call_loop
216 | }
217 |
218 | // use same coordinate space as the framework
219 | gl.Scissor(i32(a.x), i32(ctx.canvas_size.y - 1 - a.y), w, h)
220 | } else {
221 | gl.Disable(gl.SCISSOR_TEST)
222 | }
223 | }
224 |
225 | gl.DrawArrays(gl.TRIANGLES, dc.offset, dc.length)
226 | }
227 |
228 | gl.UseProgram(0)
229 | gl.ActiveTexture(gl.TEXTURE0)
230 | gl.BindTexture(gl.TEXTURE_2D, 0)
231 |
232 | return true
233 | }
234 |
235 | @(private="file", rodata)
236 | shader_vert := `
237 | precision highp float;
238 |
239 | uniform mat4 u_camera;
240 | uniform mat4 u_view;
241 | uniform mat4 u_projection;
242 |
243 | uniform vec2 u_screen_size;
244 | uniform vec2 u_mouse_pos;
245 |
246 | attribute vec3 a_pos;
247 | attribute vec4 a_col;
248 | attribute vec2 a_uv;
249 |
250 | varying vec4 v_color;
251 | varying vec2 v_uv;
252 |
253 | void main() {
254 | v_color = a_col;
255 | v_uv = a_uv;
256 | gl_Position = u_camera * vec4(a_pos, 1.0);
257 | }
258 | `
259 |
260 | @(private="file", rodata)
261 | shader_frag := `
262 | precision highp float;
263 | // precision highp sampler2D;
264 |
265 | uniform sampler2D u_texture;
266 |
267 | varying vec4 v_color;
268 | varying vec2 v_uv;
269 |
270 | void main() {
271 | vec4 tex = texture2D(u_texture, v_uv);
272 | gl_FragColor = tex.rgba * v_color.rgba;
273 | }
274 | `
275 |
276 | @(private="file")
277 | texture_filter_map := [Texture_Filter]i32{
278 | .Linear = i32(gl.LINEAR),
279 | .Nearest = i32(gl.NEAREST),
280 | }
281 | @(private="file")
282 | texture_wrap_map := [Texture_Wrap]i32{
283 | .Clamp_To_Edge = i32(gl.CLAMP_TO_EDGE),
284 | .Repeat = i32(gl.REPEAT),
285 | .Mirrored_Repeat = i32(gl.MIRRORED_REPEAT),
286 | }
287 |
288 | @(require_results)
289 | platform_texture_load_from_img :: proc(ctx: ^Context, img: Image, opts: Texture_Options) -> (tex: Texture, ok: bool) {
290 | t := gl.CreateTexture()
291 | defer if !ok {
292 | gl.DeleteTexture(t)
293 | }
294 |
295 | gl.BindTexture(gl.TEXTURE_2D, t)
296 |
297 | gl.TexImage2DSlice(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, img.pixels[:])
298 |
299 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture_filter_map[opts.filter])
300 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture_filter_map[opts.filter])
301 |
302 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, texture_wrap_map[opts.wrap[0]])
303 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, texture_wrap_map[opts.wrap[1]])
304 | gl.BindTexture(gl.TEXTURE_2D, 0)
305 |
306 | tex.handle = u32(t)
307 | tex.width = img.width
308 | tex.height = img.height
309 | ok = true
310 | return
311 | }
312 |
313 | platform_texture_unload :: proc(ctx: ^Context, tex: Texture) {
314 | gl.DeleteTexture(gl.Texture(tex.handle))
315 | }
316 |
317 |
318 | events_to_handle := [?]js.Event_Kind{
319 | .Focus,
320 | .Blur,
321 | .Mouse_Move,
322 | .Mouse_Up,
323 | .Mouse_Down,
324 | .Key_Down,
325 | .Key_Up,
326 | .Scroll,
327 |
328 | .Gamepad_Connected,
329 | .Gamepad_Disconnected,
330 |
331 | }
332 |
333 | window_wide_events := #partial [js.Event_Kind]bool {
334 | .Focus = true,
335 | .Focus_In = true,
336 | .Focus_Out = true,
337 | .Blur = true,
338 | .Key_Down = true,
339 | .Key_Up = true,
340 | .Key_Press = true,
341 |
342 | .Gamepad_Connected = true,
343 | .Gamepad_Disconnected = true,
344 |
345 | }
346 |
347 | platform_event_callback :: proc(e: js.Event) {
348 | ctx := (^Context)(e.user_data)
349 |
350 | #partial switch e.kind {
351 | case .Focus: // Enter
352 | // ignore
353 | case .Blur: // Exit
354 | ctx.io.full_reset = true
355 | case .Mouse_Move:
356 | ctx.io.mouse_pos = {i32(e.mouse.offset.x), i32(e.mouse.offset.y)}
357 | case .Mouse_Up:
358 | ctx.io.mouse_pos = {i32(e.mouse.offset.x), i32(e.mouse.offset.y)}
359 | switch e.mouse.button {
360 | case 0: ctx.io.mouse_down += {.Left}
361 | case 1: ctx.io.mouse_down += {.Middle}
362 | case 2: ctx.io.mouse_down += {.Right}
363 | }
364 | case .Mouse_Down:
365 | ctx.io.mouse_pos = {i32(e.mouse.offset.x), i32(e.mouse.offset.y)}
366 | ctx.io.mouse_pressed_pos = {i32(e.mouse.offset.x), i32(e.mouse.offset.y)}
367 | switch e.mouse.button {
368 | case 0: ctx.io.mouse_pressed += {.Left}
369 | case 1: ctx.io.mouse_pressed += {.Middle}
370 | case 2: ctx.io.mouse_pressed += {.Right}
371 | }
372 | ctx.io.mouse_down -= ctx.io.mouse_pressed
373 |
374 | case .Key_Down:
375 | if key, _ := code_to_key(e.key.code); key != .Invalid {
376 | if !e.key.repeat {
377 | ctx.io.last_key_press_time = ctx.curr_time
378 | ctx.io.key_pressed += {key}
379 |
380 | if count := &ctx.io.key_pressed_count_per_frame[key]; count^ < 255 {
381 | count^ += 1
382 | }
383 |
384 | if key not_in MODIFIER_KEYS {
385 | ctx.io.pressed_key_stroke = { ctx.io.modifiers, key }
386 | }
387 | } else {
388 | ctx.io.key_repeat += {key}
389 | }
390 | }
391 | case .Key_Up:
392 | if key, _ := code_to_key(e.key.code); key != .Invalid {
393 | ctx.io.key_released += {key}
394 | }
395 |
396 | case .Scroll:
397 | ctx.io.scroll_delta.x += i32(e.scroll.delta.x)
398 | ctx.io.scroll_delta.y += i32(e.scroll.delta.y)
399 |
400 | case .Gamepad_Connected:
401 | if ctx.io.connected_gamepad_count < MAX_GAMEPADS {
402 | for &gamepad in ctx.io.gamepads {
403 | if !gamepad.connected {
404 | gamepad = {}
405 | gamepad.connected = true
406 | gamepad.index = i32(e.gamepad.index)
407 | ctx.io.connected_gamepad_count += 1
408 | break
409 | }
410 | }
411 | }
412 | case .Gamepad_Disconnected:
413 | for &gamepad in ctx.io.gamepads {
414 | if gamepad.connected && gamepad.index == i32(e.gamepad.index) {
415 | gamepad = {}
416 | ctx.io.connected_gamepad_count -= 1
417 | break
418 | }
419 | }
420 | }
421 | }
422 |
423 |
424 | @(private)
425 | code_to_key :: proc(code: string) -> (key: Key, printable: bool) {
426 | switch code {
427 | case "KeyA": return .A, true
428 | case "KeyB": return .B, true
429 | case "KeyC": return .C, true
430 | case "KeyD": return .D, true
431 | case "KeyE": return .E, true
432 | case "KeyF": return .F, true
433 | case "KeyG": return .G, true
434 | case "KeyH": return .H, true
435 | case "KeyI": return .I, true
436 | case "KeyJ": return .J, true
437 | case "KeyK": return .K, true
438 | case "KeyL": return .L, true
439 | case "KeyM": return .M, true
440 | case "KeyN": return .N, true
441 | case "KeyO": return .O, true
442 | case "KeyP": return .P, true
443 | case "KeyQ": return .Q, true
444 | case "KeyR": return .R, true
445 | case "KeyS": return .S, true
446 | case "KeyT": return .T, true
447 | case "KeyU": return .U, true
448 | case "KeyV": return .V, true
449 | case "KeyW": return .W, true
450 | case "KeyX": return .X, true
451 | case "KeyY": return .Y, true
452 | case "KeyZ": return .Z, true
453 |
454 | case "Digit1": return .Key_1, true
455 | case "Digit2": return .Key_2, true
456 | case "Digit3": return .Key_3, true
457 | case "Digit4": return .Key_4, true
458 | case "Digit5": return .Key_5, true
459 | case "Digit6": return .Key_6, true
460 | case "Digit7": return .Key_7, true
461 | case "Digit8": return .Key_8, true
462 | case "Digit9": return .Key_9, true
463 | case "Digit0": return .Key_0, true
464 |
465 |
466 | case "Numpad1": return .Numpad_1, true
467 | case "Numpad2": return .Numpad_2, true
468 | case "Numpad3": return .Numpad_3, true
469 | case "Numpad4": return .Numpad_4, true
470 | case "Numpad5": return .Numpad_5, true
471 | case "Numpad6": return .Numpad_6, true
472 | case "Numpad7": return .Numpad_7, true
473 | case "Numpad8": return .Numpad_8, true
474 | case "Numpad9": return .Numpad_9, true
475 | case "Numpad0": return .Numpad_0, true
476 |
477 | case "NumpadDivide": return .Numpad_Divide, true
478 | case "NumpadMultiply": return .Numpad_Multiply, true
479 | case "NumpadSubtract": return .Numpad_Subtract, true
480 | case "NumpadAdd": return .Numpad_Add, true
481 | case "NumpadEnter": return .Numpad_Enter, true
482 | case "NumpadDecimal": return .Numpad_Decimal, true
483 |
484 | case "Escape": return .Escape, false
485 | case "Enter": return .Return, true
486 | case "Tab": return .Tab, false
487 | case "Backspace": return .Backspace, false
488 | case "Space": return .Space, true
489 | case "Delete": return .Delete, false
490 | case "Insert": return .Insert, false
491 |
492 | case "Quote": return .Apostrophe, true
493 | case "Comma": return .Comma, true
494 | case "Minus": return .Minus, true
495 | case "Period": return .Period, true
496 | case "Slash": return .Slash, true
497 | case "Semicolon": return .Semicolon, true
498 | case "Equal": return .Equal, true
499 | case "Backslash": return .Backslash, true
500 | case "IntlBackslash": return .Backslash, true
501 | case "BracketLeft": return .Bracket_Left, true
502 | case "BracketRight": return .Bracket_Right, true
503 | case "Backquote": return .Grave_Accent, true
504 |
505 | case "Home": return .Home, false
506 | case "End": return .End, false
507 | case "PageUp": return .Page_Up, false
508 | case "PageDown": return .Page_Down, false
509 |
510 | case "ControlLeft": return .Left_Ctrl, false
511 | case "ShiftLeft": return .Left_Shift, false
512 | case "AltLeft": return .Left_Alt, false
513 | case "ControlRight": return .Right_Ctrl, false
514 | case "ShiftRight": return .Right_Shift, false
515 | case "AltRight": return .Right_Alt, false
516 |
517 |
518 | case "ArrowUp": return .Up, false
519 | case "ArrowDown": return .Down, false
520 | case "ArrowLeft": return .Left, false
521 | case "ArrowRight": return .Right, false
522 | }
523 | return .Invalid, false
524 | }
525 |
526 |
527 |
528 | @(private="file")
529 | platform_sound_init :: proc(ctx: ^Context) -> bool {
530 | return false
531 | }
532 |
533 | @(private="file")
534 | platform_sound_step :: proc(ctx: ^Context) -> bool {
535 | return false
536 | }
537 |
538 | @(private="file")
539 | platform_sound_fini :: proc(ctx: ^Context) -> bool {
540 | return false
541 | }
--------------------------------------------------------------------------------
/crow2d/platform_windows.odin:
--------------------------------------------------------------------------------
1 | #+private
2 | package crowd2d
3 |
4 | import win32 "core:sys/windows"
5 | import glm "core:math/linalg/glsl"
6 |
7 | import gl "vendor:OpenGL"
8 |
9 | import "core:fmt"
10 | _ :: fmt
11 |
12 | // Defining these will try to prefer discrete graphics over integrated graphics
13 | @(export, link_name="NvOptimusEnablement")
14 | NvOptimusEnablement: u32 = 0x00000001
15 |
16 | @(export, link_name="AmdPowerXpressRequestHighPerformance")
17 | AmdPowerXpressRequestHighPerformance: i32 = 1
18 |
19 | #assert(size_of(int) >= size_of(uintptr))
20 |
21 | Platform_Data :: struct {
22 | wc: win32.WNDCLASSEXW,
23 | wnd: win32.HWND,
24 | dc: win32.HDC,
25 | opengl_ctx: win32.HGLRC,
26 |
27 | vao: u32,
28 |
29 | is_showing: bool,
30 | }
31 |
32 | @(private="file")
33 | platform_init_get_wgl_procedures_attempted: bool
34 | @(private="file")
35 | platform_init_get_wgl_procedures_successful: bool
36 |
37 | @(private="file")
38 | platform_init_get_wgl_procedures :: proc() -> bool {
39 | if platform_init_get_wgl_procedures_attempted {
40 | return platform_init_get_wgl_procedures_successful
41 | }
42 | platform_init_get_wgl_procedures_attempted = true
43 |
44 | dummy := win32.CreateWindowExW(0,
45 | win32.L("STATIC"), win32.L("Dummy Window"), win32.WS_OVERLAPPED,
46 | win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT,
47 | nil, nil, nil, nil,
48 | )
49 | (dummy != nil) or_return
50 | defer win32.DestroyWindow(dummy)
51 |
52 | dc := win32.GetDC(dummy)
53 | defer win32.ReleaseDC(dummy, dc)
54 |
55 | (dc != nil) or_return
56 | {
57 | pfd: win32.PIXELFORMATDESCRIPTOR
58 | pfd.nSize = size_of(pfd)
59 | pfd.nVersion = 1
60 | pfd.dwFlags = win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL | win32.PFD_DOUBLEBUFFER
61 | pfd.iPixelType = win32.PFD_TYPE_RGBA
62 | pfd.cColorBits = 32
63 | pfd.cDepthBits = 24
64 | pfd.cStencilBits = 8
65 | pfd.iLayerType = win32.PFD_MAIN_PLANE
66 | pf := win32.ChoosePixelFormat(dc, &pfd)
67 | if pf == 0 {
68 | return false
69 | }
70 | if !win32.SetPixelFormat(dc, pf, &pfd) {
71 | return false
72 | }
73 | win32.DescribePixelFormat(dc, pf, size_of(pfd), &pfd)
74 | }
75 |
76 | rc := win32.wglCreateContext(dc)
77 | (rc != nil) or_return
78 |
79 | win32.wglMakeCurrent(dc, rc) or_return
80 | defer win32.wglDeleteContext(rc)
81 | defer win32.wglMakeCurrent(nil, nil)
82 |
83 |
84 | if win32.wglCreateContextAttribsARB == nil {
85 | win32.wglCreateContextAttribsARB = auto_cast win32.wglGetProcAddress("wglCreateContextAttribsARB")
86 | win32.wglChoosePixelFormatARB = auto_cast win32.wglGetProcAddress("wglChoosePixelFormatARB")
87 | win32.wglSwapIntervalEXT = auto_cast win32.wglGetProcAddress("wglSwapIntervalEXT")
88 | win32.wglGetExtensionsStringARB = auto_cast win32.wglGetProcAddress("wglGetExtensionsStringARB")
89 | }
90 | ok := win32.wglCreateContextAttribsARB != nil &&
91 | win32.wglChoosePixelFormatARB != nil &&
92 | win32.wglSwapIntervalEXT != nil &&
93 | win32.wglGetExtensionsStringARB != nil
94 | platform_init_get_wgl_procedures_successful = ok
95 | return ok
96 | }
97 |
98 |
99 | @(require_results)
100 | platform_init :: proc(ctx: ^Context) -> bool {
101 | has_wgl_extensions := platform_init_get_wgl_procedures()
102 | if !has_wgl_extensions {
103 | fmt.eprintln("Could not initialize wgl (Windows OpenGL) extensions")
104 | return false
105 | }
106 |
107 | pd := &ctx.platform_data
108 |
109 | pd.wc.cbSize = size_of(pd.wc)
110 | pd.wc.hInstance = win32.HANDLE(win32.GetModuleHandleW(nil))
111 | pd.wc.lpfnWndProc = platform_win_proc
112 | pd.wc.lpszClassName = win32.L("Crow2DWindowClass")
113 | pd.wc.hIcon = win32.LoadIconW(nil, cast([^]u16)cast(rawptr)win32.IDI_APPLICATION)
114 | pd.wc.hCursor = win32.LoadCursorW(nil, cast([^]u16)cast(rawptr)win32.IDC_ARROW)
115 |
116 | if win32.RegisterClassExW(&pd.wc) == 0 {
117 | return false
118 | }
119 | ex_style := u32(win32.WS_EX_APPWINDOW)
120 | pd.wnd = win32.CreateWindowExW(ex_style, pd.wc.lpszClassName, win32.L("Crow2D"), win32.WS_OVERLAPPEDWINDOW, win32.CW_USEDEFAULT, win32.CW_USEDEFAULT, 1280, 800, nil, nil, pd.wc.hInstance, nil)
121 | if pd.wnd == nil {
122 | return false
123 | }
124 | win32.SetWindowLongPtrW(pd.wnd, win32.GWLP_USERDATA, int(uintptr(ctx)))
125 |
126 | _ = platform_sound_init(ctx)
127 |
128 | ctx.canvas_size.x = 1
129 | ctx.canvas_size.y = 1
130 | if rect: win32.RECT; win32.GetClientRect(pd.wnd, &rect) {
131 | ctx.canvas_size.x = max(f32(rect.right - rect.left), 1)
132 | ctx.canvas_size.y = max(f32(rect.bottom - rect.top), 1)
133 | }
134 |
135 | OPENGL_MAJOR :: 4
136 | OPENGL_MINOR :: 5
137 |
138 | pd.dc = win32.GetDC(pd.wnd)
139 |
140 | {
141 | attribs := [?]i32{
142 | win32.WGL_DRAW_TO_WINDOW_ARB, 1,
143 | win32.WGL_SUPPORT_OPENGL_ARB, 1,
144 | win32.WGL_DOUBLE_BUFFER_ARB, 1,
145 | win32.WGL_PIXEL_TYPE_ARB, win32.WGL_TYPE_RGBA_ARB,
146 | win32.WGL_COLOR_BITS_ARB, 32,
147 | win32.WGL_DEPTH_BITS_ARB, 24,
148 | win32.WGL_STENCIL_BITS_ARB, 8,
149 | 0,
150 | }
151 |
152 | format: i32
153 | formats: u32
154 | if !win32.wglChoosePixelFormatARB(pd.dc, &attribs[0], nil, 1, &format, &formats) || formats == 0 {
155 | return false
156 | }
157 |
158 | pfd: win32.PIXELFORMATDESCRIPTOR
159 | pfd.nSize = size_of(pfd)
160 | pfd.nVersion = 1
161 | pfd.dwFlags = win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL | win32.PFD_DOUBLEBUFFER
162 | pfd.iPixelType = win32.PFD_TYPE_RGBA
163 | pfd.cColorBits = 32
164 | pfd.cDepthBits = 24
165 | pfd.cStencilBits = 8
166 | pfd.iLayerType = win32.PFD_MAIN_PLANE
167 | pf := win32.ChoosePixelFormat(pd.dc, &pfd)
168 | if pf == 0 {
169 | return false
170 | }
171 | if !win32.SetPixelFormat(pd.dc, pf, &pfd) {
172 | return false
173 | }
174 | win32.DescribePixelFormat(pd.dc, pf, size_of(pfd), &pfd)
175 | }
176 |
177 | attribs := [?]i32{
178 | win32.WGL_CONTEXT_MAJOR_VERSION_ARB, OPENGL_MAJOR,
179 | win32.WGL_CONTEXT_MINOR_VERSION_ARB, OPENGL_MINOR,
180 | win32.WGL_CONTEXT_FLAGS_ARB, win32.CONTEXT_CORE_PROFILE_BIT_ARB,
181 | 0, 0,
182 | 0,
183 | }
184 | if ODIN_DEBUG {
185 | attribs[len(attribs)-3] = win32.WGL_CONTEXT_FLAGS_ARB
186 | attribs[len(attribs)-2] = win32.WGL_CONTEXT_DEBUG_BIT_ARB
187 | }
188 |
189 | pd.opengl_ctx = win32.wglCreateContextAttribsARB(pd.dc, nil, &attribs[0])
190 | win32.wglMakeCurrent(pd.dc, pd.opengl_ctx) or_return
191 |
192 | gl.load_up_to(OPENGL_MAJOR, OPENGL_MINOR, win32.gl_set_proc_address)
193 |
194 | win32.wglSwapIntervalEXT(1)
195 |
196 | gl.GenVertexArrays(1, &pd.vao)
197 | gl.BindVertexArray(pd.vao)
198 |
199 | ctx.default_shader.handle = gl.load_shaders_source(shader_vert, shader_frag) or_return
200 |
201 | gl.GenBuffers(1, &ctx.vertex_buffer.handle)
202 | gl.BindBuffer(gl.ARRAY_BUFFER, ctx.vertex_buffer.handle)
203 | gl.BufferData(gl.ARRAY_BUFFER, len(ctx.vertices)*size_of(ctx.vertices[0]), nil, gl.DYNAMIC_DRAW)
204 |
205 | ctx.default_texture = texture_load_default_white(ctx) or_return
206 |
207 | return true
208 | }
209 |
210 | platform_fini :: proc(ctx: ^Context) {
211 | pd := &ctx.platform_data
212 | _ = win32.wglMakeCurrent(pd.dc, pd.opengl_ctx)
213 |
214 | texture_unload(ctx, ctx.default_texture)
215 |
216 | gl.DeleteBuffers(1, &ctx.vertex_buffer.handle)
217 | gl.DeleteProgram(ctx.default_shader.handle)
218 |
219 | gl.DeleteVertexArrays(1, &pd.vao)
220 | win32.wglDeleteContext(pd.opengl_ctx)
221 |
222 | _ = platform_sound_fini(ctx)
223 |
224 | // win32.ReleaseDC(pd.wnd, pd.dc)
225 | win32.DestroyWindow(pd.wnd)
226 | }
227 |
228 | @(require_results)
229 | platform_update :: proc(ctx: ^Context) -> bool {
230 | process_key :: proc(ctx: ^Context, key: Key, pressed: bool) {
231 | if pressed {
232 | ctx.io.last_key_press_time = ctx.curr_time
233 | ctx.io.key_pressed += {key}
234 |
235 | if count := &ctx.io.key_pressed_count_per_frame[key]; count^ < 255 {
236 | count^ += 1
237 | }
238 | } else { // released
239 | ctx.io.key_released += {key}
240 | }
241 | }
242 |
243 |
244 | pd := &ctx.platform_data
245 | if !pd.is_showing {
246 | pd.is_showing = true
247 | win32.ShowWindow(pd.wnd, 1)
248 | }
249 |
250 | ctx.canvas_size.x = 1
251 | ctx.canvas_size.y = 1
252 | if rect: win32.RECT; win32.GetClientRect(pd.wnd, &rect) {
253 | ctx.canvas_size.x = max(f32(rect.right - rect.left), 1)
254 | ctx.canvas_size.y = max(f32(rect.bottom - rect.top), 1)
255 | }
256 |
257 | for {
258 | msg: win32.MSG
259 | win32.PeekMessageW(&msg, nil, 0, 0, win32.PM_REMOVE) or_break
260 |
261 | switch msg.message {
262 | case win32.WM_QUIT:
263 | ctx.is_done = true
264 |
265 | case win32.WM_SYSKEYDOWN, win32.WM_SYSKEYUP, win32.WM_KEYDOWN, win32.WM_KEYUP:
266 | vk_code := u32(msg.wParam)
267 | is_extended := msg.lParam & 0x01000000 != 0
268 |
269 | switch vk_code {
270 | case win32.VK_CONTROL, win32.VK_MENU, win32.VK_SHIFT:
271 | scan_code := win32.HIWORD(msg.lParam)
272 | if is_extended {
273 | scan_code = win32.MAKEWORD(scan_code, 0xe0)
274 | }
275 |
276 | vk_code = u32(win32.MapVirtualKeyW(u32(scan_code), win32.MAPVK_VSC_TO_VK_EX))
277 | }
278 |
279 |
280 | was_down := msg.lParam & (1<<30) != 0
281 | is_down := msg.lParam & (1<<31) == 0
282 |
283 | if was_down != is_down {
284 | switch vk_code {
285 | case win32.VK_LCONTROL: process_key(ctx, .Left_Ctrl, is_down)
286 | case win32.VK_RCONTROL: process_key(ctx, .Right_Ctrl, is_down)
287 | case win32.VK_LMENU: process_key(ctx, .Left_Alt, is_down)
288 | case win32.VK_RMENU: process_key(ctx, .Right_Alt, is_down)
289 | case win32.VK_LSHIFT: process_key(ctx, .Left_Shift, is_down)
290 | case win32.VK_RSHIFT: process_key(ctx, .Right_Shift, is_down)
291 |
292 | case win32.VK_RETURN:
293 | process_key(ctx, .Numpad_Enter if is_extended else .Return, is_down)
294 |
295 | case win32.VK_ESCAPE: process_key(ctx, .Escape, is_down)
296 | case win32.VK_TAB: process_key(ctx, .Tab, is_down)
297 | case win32.VK_SPACE: process_key(ctx, .Space, is_down)
298 | case win32.VK_DELETE: process_key(ctx, .Delete, is_down)
299 | case win32.VK_INSERT: process_key(ctx, .Insert, is_down)
300 |
301 | case win32.VK_OEM_7: process_key(ctx, .Apostrophe, is_down)
302 | case win32.VK_OEM_COMMA: process_key(ctx, .Comma, is_down)
303 | case win32.VK_OEM_MINUS: process_key(ctx, .Minus, is_down)
304 | case win32.VK_OEM_PERIOD: process_key(ctx, .Period, is_down)
305 | case win32.VK_OEM_2: process_key(ctx, .Slash, is_down)
306 | case win32.VK_OEM_1: process_key(ctx, .Semicolon, is_down)
307 | case win32.VK_OEM_PLUS: process_key(ctx, .Equal, is_down)
308 | case win32.VK_OEM_5: process_key(ctx, .Backslash, is_down)
309 | case win32.VK_OEM_4: process_key(ctx, .Bracket_Left, is_down)
310 | case win32.VK_OEM_6: process_key(ctx, .Bracket_Right, is_down)
311 | case win32.VK_OEM_3: process_key(ctx, .Grave_Accent, is_down)
312 |
313 | case win32.VK_HOME: process_key(ctx, .Home, is_down)
314 | case win32.VK_END: process_key(ctx, .End, is_down)
315 | case win32.VK_PRIOR: process_key(ctx, .Page_Up, is_down)
316 | case win32.VK_NEXT: process_key(ctx, .Page_Down, is_down)
317 |
318 | case 'A'..='Z': process_key(ctx, .A + Key(vk_code-'A'), is_down)
319 | case '0'..='9': process_key(ctx, .Key_0 + Key(vk_code-'0'), is_down)
320 |
321 | case win32.VK_NUMPAD0..=win32.VK_NUMPAD9: process_key(ctx, .Numpad_0 + Key(vk_code-win32.VK_NUMPAD0), is_down)
322 |
323 | case win32.VK_DIVIDE: process_key(ctx, .Numpad_Divide, is_down)
324 | case win32.VK_MULTIPLY: process_key(ctx, .Numpad_Multiply, is_down)
325 | case win32.VK_SUBTRACT: process_key(ctx, .Numpad_Subtract, is_down)
326 | case win32.VK_ADD: process_key(ctx, .Numpad_Add, is_down)
327 | case win32.VK_DECIMAL: process_key(ctx, .Numpad_Decimal, is_down)
328 |
329 | case win32.VK_UP: process_key(ctx, .Up, is_down)
330 | case win32.VK_DOWN: process_key(ctx, .Down, is_down)
331 | case win32.VK_LEFT: process_key(ctx, .Left, is_down)
332 | case win32.VK_RIGHT: process_key(ctx, .Right, is_down)
333 | }
334 | }
335 |
336 | case win32.WM_MOUSEWHEEL: ctx.io.scroll_delta.y = i32(win32.GET_WHEEL_DELTA_WPARAM(msg.wParam))
337 | case win32.WM_MOUSEHWHEEL: ctx.io.scroll_delta.x = i32(win32.GET_WHEEL_DELTA_WPARAM(msg.wParam))
338 |
339 | case:
340 | win32.TranslateMessage(&msg)
341 | win32.DispatchMessageW(&msg)
342 | }
343 | }
344 |
345 | mouse_stuff: { // Mouse stuff
346 | if point: win32.POINT; win32.GetCursorPos(&point) {
347 | if win32.ScreenToClient(pd.wnd, &point) {
348 | ctx.io.mouse_pos.x = i32(point.x)
349 | ctx.io.mouse_pos.y = i32(point.y)
350 | }
351 |
352 | }
353 | if ctx.io.mouse_pos.x < 0 || ctx.io.mouse_pos.y < 0 {
354 | break mouse_stuff
355 | }
356 |
357 | if ctx.io.mouse_pos.x >= i32(ctx.canvas_size.x) || ctx.io.mouse_pos.y >= i32(ctx.canvas_size.y) {
358 | break mouse_stuff
359 | }
360 |
361 | if win32.GetAsyncKeyState(win32.VK_LBUTTON) != 0 {
362 | ctx.io.mouse_down += {.Left}
363 | if .Left not_in ctx.io.internal_mouse_was_down {
364 | ctx.io.mouse_pressed_pos = ctx.io.mouse_pos
365 | }
366 | }
367 | if win32.GetAsyncKeyState(win32.VK_RBUTTON) != 0 {
368 | ctx.io.mouse_down += {.Right}
369 | if .Right not_in ctx.io.internal_mouse_was_down {
370 | ctx.io.mouse_pressed_pos = ctx.io.mouse_pos
371 | }
372 | }
373 | if win32.GetAsyncKeyState(win32.VK_MBUTTON) != 0 {
374 | ctx.io.mouse_down += {.Middle}
375 | if .Middle not_in ctx.io.internal_mouse_was_down {
376 | ctx.io.mouse_pressed_pos = ctx.io.mouse_pos
377 | }
378 | }
379 | }
380 |
381 | _ = platform_sound_step(ctx)
382 |
383 | return true
384 | }
385 |
386 | @(require_results)
387 | platform_draw :: proc(ctx: ^Context) -> bool {
388 | enable_shader_state :: proc(ctx: ^Context, shader: u32, camera: Camera, width, height: i32) -> (mvp: glm.mat4) {
389 | gl.UseProgram(shader)
390 |
391 | a_pos := u32(gl.GetAttribLocation(shader, "a_pos"))
392 | gl.EnableVertexAttribArray(a_pos)
393 | gl.VertexAttribPointer(a_pos, 3, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, pos))
394 |
395 | a_col := u32(gl.GetAttribLocation(shader, "a_col"))
396 | gl.EnableVertexAttribArray(a_col)
397 | gl.VertexAttribPointer(a_col, 4, gl.UNSIGNED_BYTE, true, size_of(Vertex), offset_of(Vertex, col))
398 |
399 | a_uv := u32(gl.GetAttribLocation(shader, "a_uv"))
400 | gl.EnableVertexAttribArray(a_uv)
401 | gl.VertexAttribPointer(a_uv, 2, gl.FLOAT, false, size_of(Vertex), offset_of(Vertex, uv))
402 |
403 | {
404 | proj := glm.mat4Ortho3d(0, f32(width), f32(height), 0, camera.near, camera.far)
405 |
406 | origin := glm.mat4Translate({-camera.target.x, -camera.target.y, 0})
407 | rotation := glm.mat4Rotate({0, 0, 1}, camera.rotation)
408 | scale := glm.mat4Scale({camera.zoom, camera.zoom, 1})
409 | translation := glm.mat4Translate({camera.offset.x, camera.offset.y, 0})
410 |
411 | view := origin * scale * rotation * translation
412 |
413 | mvp = proj * view
414 |
415 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_camera"), 1, false, &mvp[0, 0])
416 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_view"), 1, false, &view[0, 0])
417 | gl.UniformMatrix4fv(gl.GetUniformLocation(shader, "u_projection"), 1, false, &proj[0, 0])
418 | }
419 | gl.Uniform2f(gl.GetUniformLocation(shader, "u_screen_size"), f32(width), f32(height))
420 | gl.Uniform2f(gl.GetUniformLocation(shader, "u_mouse_pos"), f32(ctx.io.mouse_pos.x), f32(ctx.io.mouse_pos.y))
421 |
422 |
423 | gl.Uniform1i(gl.GetUniformLocation(shader, "u_texture"), 0)
424 |
425 | return
426 | }
427 |
428 | pd := &ctx.platform_data
429 | win32.wglMakeCurrent(pd.dc, pd.opengl_ctx) or_return
430 | defer win32.SwapBuffers(pd.dc)
431 |
432 | gl.BindVertexArray(pd.vao)
433 |
434 | gl.BindBuffer(gl.ARRAY_BUFFER, ctx.vertex_buffer.handle)
435 | gl.BufferData(gl.ARRAY_BUFFER, len(ctx.vertices)*size_of(ctx.vertices[0]), raw_data(ctx.vertices), gl.DYNAMIC_DRAW)
436 |
437 |
438 | width, height := i32(ctx.canvas_size.x), i32(ctx.canvas_size.y)
439 |
440 | gl.Viewport(0, 0, width, height)
441 | gl.ClearColor(f32(ctx.clear_color.r)/255, f32(ctx.clear_color.g)/255, f32(ctx.clear_color.b)/255, f32(ctx.clear_color.a)/255)
442 | gl.Disable(gl.DEPTH_TEST)
443 | gl.Enable(gl.BLEND)
444 | gl.Enable(gl.CULL_FACE)
445 | gl.CullFace(gl.BACK)
446 | gl.FrontFace(gl.CW)
447 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
448 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
449 |
450 |
451 | prev_draw_call := Draw_Call{}
452 | prev_draw_call.shader = SHADER_INVALID
453 | prev_draw_call.texture = TEXTURE_INVALID
454 | mvp: glm.mat4
455 |
456 | draw_call_loop: for dc in ctx.draw_calls {
457 | defer prev_draw_call = dc
458 |
459 | if prev_draw_call.depth_test != dc.depth_test {
460 | if dc.depth_test {
461 | gl.Enable(gl.DEPTH_TEST)
462 | } else {
463 | gl.Disable(gl.DEPTH_TEST)
464 | }
465 | }
466 |
467 | if prev_draw_call.texture != dc.texture {
468 | gl.ActiveTexture(gl.TEXTURE0)
469 | gl.BindTexture(gl.TEXTURE_2D, dc.texture.handle)
470 | }
471 |
472 | if prev_draw_call.shader != dc.shader {
473 | mvp = enable_shader_state(ctx, dc.shader.handle, ctx.camera, width, height)
474 | }
475 |
476 | if prev_draw_call.clip_rect != dc.clip_rect {
477 | if r, ok := dc.clip_rect.?; ok {
478 | gl.Enable(gl.SCISSOR_TEST)
479 | a := r.pos
480 | b := r.pos + r.size
481 |
482 | a.x = clamp(a.x, 0, ctx.canvas_size.x-1)
483 | a.y = clamp(a.y, 0, ctx.canvas_size.y-1)
484 |
485 | b.x = clamp(b.x, 0, ctx.canvas_size.x-1)
486 | b.y = clamp(b.y, 0, ctx.canvas_size.y-1)
487 |
488 | w := i32(b.x-a.x)
489 | h := i32(b.y-a.y)
490 |
491 | if w <= 0 || h <= 0 {
492 | continue draw_call_loop
493 | }
494 |
495 | // use same coordinate space as the framework
496 | gl.Scissor(i32(a.x), i32(ctx.canvas_size.y - 1 - a.y), w, h)
497 | } else {
498 | gl.Disable(gl.SCISSOR_TEST)
499 | }
500 | }
501 |
502 | gl.DrawArrays(gl.TRIANGLES, i32(dc.offset), i32(dc.length))
503 | }
504 |
505 |
506 | gl.UseProgram(0)
507 | gl.ActiveTexture(gl.TEXTURE0)
508 | gl.BindTexture(gl.TEXTURE_2D, 0)
509 |
510 | return true
511 | }
512 |
513 |
514 | @(private="file", rodata)
515 | shader_vert := `#version 120
516 | precision highp float;
517 |
518 | uniform mat4 u_camera;
519 | uniform mat4 u_view;
520 | uniform mat4 u_projection;
521 |
522 | uniform vec2 u_screen_size;
523 | uniform vec2 u_mouse_pos;
524 |
525 | attribute vec3 a_pos;
526 | attribute vec4 a_col;
527 | attribute vec2 a_uv;
528 |
529 | varying vec4 v_color;
530 | varying vec2 v_uv;
531 |
532 | void main() {
533 | v_color = a_col;
534 | v_uv = a_uv;
535 | gl_Position = u_camera * vec4(a_pos, 1.0);
536 | }
537 | `
538 |
539 | @(private="file", rodata)
540 | shader_frag := `#version 120
541 | precision highp float;
542 |
543 | uniform sampler2D u_texture;
544 |
545 | varying vec4 v_color;
546 | varying vec2 v_uv;
547 |
548 | void main() {
549 | vec4 tex = texture2D(u_texture, v_uv);
550 | gl_FragColor = tex.rgba * v_color.rgba;
551 |
552 | }
553 | `
554 |
555 | @(private="file")
556 | texture_filter_map := [Texture_Filter]i32{
557 | .Linear = i32(gl.LINEAR),
558 | .Nearest = i32(gl.NEAREST),
559 | }
560 | @(private="file")
561 | texture_wrap_map := [Texture_Wrap]i32{
562 | .Clamp_To_Edge = i32(gl.CLAMP_TO_EDGE),
563 | .Repeat = i32(gl.REPEAT),
564 | .Mirrored_Repeat = i32(gl.MIRRORED_REPEAT),
565 | }
566 |
567 |
568 | @(require_results)
569 | platform_texture_load_from_img :: proc(ctx: ^Context, img: Image, opts: Texture_Options) -> (tex: Texture, ok: bool) {
570 | t: u32
571 | gl.GenTextures(1, &t)
572 | defer if !ok {
573 | gl.DeleteTextures(1, &t)
574 | }
575 | assert(t != 0)
576 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
577 |
578 | gl.BindTexture(gl.TEXTURE_2D, t)
579 |
580 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture_filter_map[opts.filter])
581 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture_filter_map[opts.filter])
582 |
583 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, texture_wrap_map[opts.wrap[0]])
584 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, texture_wrap_map[opts.wrap[1]])
585 |
586 | gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, raw_data(img.pixels))
587 | gl.BindTexture(gl.TEXTURE_2D, 0)
588 |
589 | tex.handle = u32(t)
590 | tex.width = img.width
591 | tex.height = img.height
592 | ok = true
593 | return
594 | }
595 |
596 | platform_texture_unload :: proc(ctx: ^Context, tex: Texture) {
597 | win32.wglMakeCurrent(ctx.platform_data.dc, ctx.platform_data.opengl_ctx)
598 | t := tex.handle
599 | gl.DeleteTextures(1, &t)
600 | }
601 |
602 | @(private="file")
603 | platform_win_proc :: proc "system" (hwnd: win32.HWND, Msg: win32.UINT, wParam: win32.WPARAM, lParam: win32.LPARAM) -> win32.LRESULT {
604 | ctx := (^Context)(uintptr(win32.GetWindowLongPtrW(hwnd, win32.GWLP_USERDATA)))
605 | if ctx == nil {
606 | return win32.DefWindowProcW(hwnd, Msg, wParam, lParam)
607 | }
608 | assert_contextless(ctx.platform_data.wnd == hwnd)
609 |
610 | switch Msg {
611 | case win32.WM_DESTROY:
612 | ctx.is_done = true
613 | win32.PostQuitMessage(0)
614 |
615 | case win32.WM_PAINT:
616 | ps: win32.PAINTSTRUCT
617 | hdc := win32.BeginPaint(hwnd, &ps)
618 | _ = hdc
619 | // win32.FillRect(hdc, &ps.rcPaint, win32.HBRUSH(uintptr(win32.COLOR_WINDOW+1)))
620 | win32.EndPaint(hwnd, &ps)
621 |
622 | case:
623 | return win32.DefWindowProcW(hwnd, Msg, wParam, lParam)
624 | }
625 | return 0
626 |
627 | }
628 |
629 | @(private="file")
630 | platform_sound_init :: proc(ctx: ^Context) -> bool {
631 | return false
632 | }
633 |
634 | @(private="file")
635 | platform_sound_step :: proc(ctx: ^Context) -> bool {
636 | return false
637 | }
638 |
639 | @(private="file")
640 | platform_sound_fini :: proc(ctx: ^Context) -> bool {
641 | return false
642 | }
--------------------------------------------------------------------------------
/odin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (function() {
4 |
5 | function getElement(name) {
6 | if (name) {
7 | return document.getElementById(name);
8 | }
9 | return undefined;
10 | }
11 |
12 | function stripNewline(str) {
13 | return str.replace(/\n/, ' ')
14 | }
15 |
16 | class WasmMemoryInterface {
17 | constructor() {
18 | this.memory = null;
19 | this.exports = null;
20 | this.listenerMap = {};
21 |
22 | // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32`
23 | this.intSize = 4;
24 | }
25 |
26 | setIntSize(size) {
27 | this.intSize = size;
28 | }
29 |
30 | setMemory(memory) {
31 | this.memory = memory;
32 | }
33 |
34 | setExports(exports) {
35 | this.exports = exports;
36 | }
37 |
38 | get mem() {
39 | return new DataView(this.memory.buffer);
40 | }
41 |
42 |
43 | loadF32Array(addr, len) {
44 | let array = new Float32Array(this.memory.buffer, addr, len);
45 | return array;
46 | }
47 | loadF64Array(addr, len) {
48 | let array = new Float64Array(this.memory.buffer, addr, len);
49 | return array;
50 | }
51 | loadU32Array(addr, len) {
52 | let array = new Uint32Array(this.memory.buffer, addr, len);
53 | return array;
54 | }
55 | loadI32Array(addr, len) {
56 | let array = new Int32Array(this.memory.buffer, addr, len);
57 | return array;
58 | }
59 |
60 |
61 | loadU8(addr) { return this.mem.getUint8 (addr); }
62 | loadI8(addr) { return this.mem.getInt8 (addr); }
63 | loadU16(addr) { return this.mem.getUint16 (addr, true); }
64 | loadI16(addr) { return this.mem.getInt16 (addr, true); }
65 | loadU32(addr) { return this.mem.getUint32 (addr, true); }
66 | loadI32(addr) { return this.mem.getInt32 (addr, true); }
67 | loadU64(addr) {
68 | const lo = this.mem.getUint32(addr + 0, true);
69 | const hi = this.mem.getUint32(addr + 4, true);
70 | return lo + hi*4294967296;
71 | };
72 | loadI64(addr) {
73 | const lo = this.mem.getUint32(addr + 0, true);
74 | const hi = this.mem.getInt32 (addr + 4, true);
75 | return lo + hi*4294967296;
76 | };
77 | loadF32(addr) { return this.mem.getFloat32(addr, true); }
78 | loadF64(addr) { return this.mem.getFloat64(addr, true); }
79 | loadInt(addr) {
80 | if (this.intSize == 8) {
81 | return this.loadI64(addr);
82 | } else if (this.intSize == 4) {
83 | return this.loadI32(addr);
84 | } else {
85 | throw new Error('Unhandled `intSize`, expected `4` or `8`');
86 | }
87 | };
88 | loadUint(addr) {
89 | if (this.intSize == 8) {
90 | return this.loadU64(addr);
91 | } else if (this.intSize == 4) {
92 | return this.loadU32(addr);
93 | } else {
94 | throw new Error('Unhandled `intSize`, expected `4` or `8`');
95 | }
96 | };
97 | loadPtr(addr) { return this.loadU32(addr); }
98 |
99 | loadB32(addr) {
100 | return this.loadU32(addr) != 0;
101 | }
102 |
103 | loadBytes(ptr, len) {
104 | return new Uint8Array(this.memory.buffer, ptr, Number(len));
105 | }
106 |
107 | loadString(ptr, len) {
108 | const bytes = this.loadBytes(ptr, Number(len));
109 | return new TextDecoder().decode(bytes);
110 | }
111 |
112 | loadCstring(ptr) {
113 | const start = this.loadPtr(ptr);
114 | if (start == 0) {
115 | return null;
116 | }
117 | let len = 0;
118 | for (; this.mem.getUint8(start+len) != 0; len += 1) {}
119 | return this.loadString(start, len);
120 | }
121 |
122 | storeU8(addr, value) { this.mem.setUint8 (addr, value); }
123 | storeI8(addr, value) { this.mem.setInt8 (addr, value); }
124 | storeU16(addr, value) { this.mem.setUint16 (addr, value, true); }
125 | storeI16(addr, value) { this.mem.setInt16 (addr, value, true); }
126 | storeU32(addr, value) { this.mem.setUint32 (addr, value, true); }
127 | storeI32(addr, value) { this.mem.setInt32 (addr, value, true); }
128 | storeU64(addr, value) {
129 | this.mem.setUint32(addr + 0, Number(value), true);
130 |
131 | let div = 4294967296;
132 | if (typeof value == 'bigint') {
133 | div = BigInt(div);
134 | }
135 |
136 | this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true);
137 | }
138 | storeI64(addr, value) {
139 | this.mem.setUint32(addr + 0, Number(value), true);
140 |
141 | let div = 4294967296;
142 | if (typeof value == 'bigint') {
143 | div = BigInt(div);
144 | }
145 |
146 | this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true);
147 | }
148 | storeF32(addr, value) { this.mem.setFloat32(addr, value, true); }
149 | storeF64(addr, value) { this.mem.setFloat64(addr, value, true); }
150 | storeInt(addr, value) {
151 | if (this.intSize == 8) {
152 | this.storeI64(addr, value);
153 | } else if (this.intSize == 4) {
154 | this.storeI32(addr, value);
155 | } else {
156 | throw new Error('Unhandled `intSize`, expected `4` or `8`');
157 | }
158 | }
159 | storeUint(addr, value) {
160 | if (this.intSize == 8) {
161 | this.storeU64(addr, value);
162 | } else if (this.intSize == 4) {
163 | this.storeU32(addr, value);
164 | } else {
165 | throw new Error('Unhandled `intSize`, expected `4` or `8`');
166 | }
167 | }
168 |
169 | // Returned length might not be the same as `value.length` if non-ascii strings are given.
170 | storeString(addr, value) {
171 | const src = new TextEncoder().encode(value);
172 | const dst = new Uint8Array(this.memory.buffer, addr, src.length);
173 | dst.set(src);
174 | return src.length;
175 | }
176 | };
177 |
178 | class WebGLInterface {
179 | constructor(wasmMemoryInterface) {
180 | this.wasmMemoryInterface = wasmMemoryInterface;
181 | this.ctxElement = null;
182 | this.ctx = null;
183 | this.ctxVersion = 1.0;
184 | this.counter = 1;
185 | this.lastError = 0;
186 | this.buffers = [];
187 | this.mappedBuffers = {};
188 | this.programs = [];
189 | this.framebuffers = [];
190 | this.renderbuffers = [];
191 | this.textures = [];
192 | this.uniforms = [];
193 | this.shaders = [];
194 | this.vaos = [];
195 | this.contexts = [];
196 | this.currentContext = null;
197 | this.offscreenCanvases = {};
198 | this.timerQueriesEXT = [];
199 | this.queries = [];
200 | this.samplers = [];
201 | this.transformFeedbacks = [];
202 | this.syncs = [];
203 | this.programInfos = {};
204 | }
205 |
206 | get mem() {
207 | return this.wasmMemoryInterface
208 | }
209 |
210 | setCurrentContext(element, contextSettings) {
211 | if (!element) {
212 | return false;
213 | }
214 | if (this.ctxElement == element) {
215 | return true;
216 | }
217 |
218 | contextSettings = contextSettings ?? {};
219 | this.ctx = element.getContext("webgl2", contextSettings) || element.getContext("webgl", contextSettings);
220 | if (!this.ctx) {
221 | return false;
222 | }
223 | this.ctxElement = element;
224 | if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) {
225 | this.ctxVersion = 2.0;
226 | } else {
227 | this.ctxVersion = 1.0;
228 | }
229 | return true;
230 | }
231 |
232 | assertWebGL2() {
233 | if (this.ctxVersion < 2) {
234 | throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context");
235 | }
236 | }
237 | getNewId(table) {
238 | for (var ret = this.counter++, i = table.length; i < ret; i++) {
239 | table[i] = null;
240 | }
241 | return ret;
242 | }
243 | recordError(errorCode) {
244 | this.lastError || (this.lastError = errorCode);
245 | }
246 | populateUniformTable(program) {
247 | let p = this.programs[program];
248 | this.programInfos[program] = {
249 | uniforms: {},
250 | maxUniformLength: 0,
251 | maxAttributeLength: -1,
252 | maxUniformBlockNameLength: -1,
253 | };
254 | for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) {
255 | let u = this.ctx.getActiveUniform(p, i);
256 | let name = u.name;
257 | if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) {
258 | name = name.slice(0, name.lastIndexOf("["));
259 | }
260 | let loc = this.ctx.getUniformLocation(p, name);
261 | if (loc !== null) {
262 | let id = this.getNewId(this.uniforms);
263 | utable[name] = [u.size, id], this.uniforms[id] = loc;
264 | for (let j = 1; j < u.size; ++j) {
265 | let n = name + "[" + j + "]";
266 | let loc = this.ctx.getUniformLocation(p, n);
267 | let id = this.getNewId(this.uniforms);
268 | this.uniforms[id] = loc;
269 | }
270 | }
271 | }
272 | }
273 | getSource(shader, strings_ptr, strings_length) {
274 | const stringSize = this.mem.intSize*2;
275 | let source = "";
276 | for (let i = 0; i < strings_length; i++) {
277 | let ptr = this.mem.loadPtr(strings_ptr + i*stringSize);
278 | let len = this.mem.loadPtr(strings_ptr + i*stringSize + 4);
279 | let str = this.mem.loadString(ptr, len);
280 | source += str;
281 | }
282 | return source;
283 | }
284 |
285 | getWebGL1Interface() {
286 | return {
287 | SetCurrentContextById: (name_ptr, name_len) => {
288 | let name = this.mem.loadString(name_ptr, name_len);
289 | let element = getElement(name);
290 | return this.setCurrentContext(element, {alpha: true, antialias: true, depth: true, premultipliedAlpha: true});
291 | },
292 | CreateCurrentContextById: (name_ptr, name_len, attributes) => {
293 | let name = this.mem.loadString(name_ptr, name_len);
294 | let element = getElement(name);
295 |
296 | let contextSettings = {
297 | alpha: !(attributes & (1<<0)),
298 | antialias: !(attributes & (1<<1)),
299 | depth: !(attributes & (1<<2)),
300 | failIfMajorPerformanceCaveat: !!(attributes & (1<<3)),
301 | premultipliedAlpha: !(attributes & (1<<4)),
302 | preserveDrawingBuffer: !!(attributes & (1<<5)),
303 | stencil: !!(attributes & (1<<6)),
304 | desynchronized: !!(attributes & (1<<7)),
305 | };
306 |
307 | return this.setCurrentContext(element, contextSettings);
308 | },
309 | GetCurrentContextAttributes: () => {
310 | if (!this.ctx) {
311 | return 0;
312 | }
313 | let attrs = this.ctx.getContextAttributes();
314 | let res = 0;
315 | if (!attrs.alpha) res |= 1<<0;
316 | if (!attrs.antialias) res |= 1<<1;
317 | if (!attrs.depth) res |= 1<<2;
318 | if (attrs.failIfMajorPerformanceCaveat) res |= 1<<3;
319 | if (!attrs.premultipliedAlpha) res |= 1<<4;
320 | if (attrs.preserveDrawingBuffer) res |= 1<<5;
321 | if (attrs.stencil) res |= 1<<6;
322 | if (attrs.desynchronized) res |= 1<<7;
323 | return res;
324 | },
325 |
326 | DrawingBufferWidth: () => this.ctx.drawingBufferWidth,
327 | DrawingBufferHeight: () => this.ctx.drawingBufferHeight,
328 |
329 | IsExtensionSupported: (name_ptr, name_len) => {
330 | let name = this.mem.loadString(name_ptr, name_len);
331 | let extensions = this.ctx.getSupportedExtensions();
332 | return extensions.indexOf(name) !== -1
333 | },
334 |
335 |
336 | GetError: () => {
337 | let err = this.lastError;
338 | this.recordError(0);
339 | if (err) {
340 | return err;
341 | }
342 | return this.ctx.getError();
343 | },
344 |
345 | GetWebGLVersion: (major_ptr, minor_ptr) => {
346 | let version = this.ctx.getParameter(0x1F02);
347 | if (version.indexOf("WebGL 2.0") !== -1) {
348 | this.mem.storeI32(major_ptr, 2);
349 | this.mem.storeI32(minor_ptr, 0);
350 | return;
351 | }
352 |
353 | this.mem.storeI32(major_ptr, 1);
354 | this.mem.storeI32(minor_ptr, 0);
355 | },
356 | GetESVersion: (major_ptr, minor_ptr) => {
357 | let version = this.ctx.getParameter(0x1F02);
358 | if (version.indexOf("OpenGL ES 3.0") !== -1) {
359 | this.mem.storeI32(major_ptr, 3);
360 | this.mem.storeI32(minor_ptr, 0);
361 | return;
362 | }
363 |
364 | this.mem.storeI32(major_ptr, 2);
365 | this.mem.storeI32(minor_ptr, 0);
366 | },
367 |
368 |
369 | ActiveTexture: (x) => {
370 | this.ctx.activeTexture(x);
371 | },
372 | AttachShader: (program, shader) => {
373 | this.ctx.attachShader(this.programs[program], this.shaders[shader]);
374 | },
375 | BindAttribLocation: (program, index, name_ptr, name_len) => {
376 | let name = this.mem.loadString(name_ptr, name_len);
377 | this.ctx.bindAttribLocation(this.programs[program], index, name)
378 | },
379 | BindBuffer: (target, buffer) => {
380 | let bufferObj = buffer ? this.buffers[buffer] : null;
381 | if (target == 35051) {
382 | this.ctx.currentPixelPackBufferBinding = buffer;
383 | } else {
384 | if (target == 35052) {
385 | this.ctx.currentPixelUnpackBufferBinding = buffer;
386 | }
387 | this.ctx.bindBuffer(target, bufferObj)
388 | }
389 | },
390 | BindFramebuffer: (target, framebuffer) => {
391 | this.ctx.bindFramebuffer(target, framebuffer ? this.framebuffers[framebuffer] : null)
392 | },
393 | BindTexture: (target, texture) => {
394 | this.ctx.bindTexture(target, texture ? this.textures[texture] : null)
395 | },
396 | BlendColor: (red, green, blue, alpha) => {
397 | this.ctx.blendColor(red, green, blue, alpha);
398 | },
399 | BlendEquation: (mode) => {
400 | this.ctx.blendEquation(mode);
401 | },
402 | BlendFunc: (sfactor, dfactor) => {
403 | this.ctx.blendFunc(sfactor, dfactor);
404 | },
405 | BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => {
406 | this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
407 | },
408 |
409 |
410 | BufferData: (target, size, data, usage) => {
411 | if (data) {
412 | this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage);
413 | } else {
414 | this.ctx.bufferData(target, size, usage);
415 | }
416 | },
417 | BufferSubData: (target, offset, size, data) => {
418 | if (data) {
419 | this.ctx.bufferSubData(target, offset, this.mem.loadBytes(data, size));
420 | } else {
421 | this.ctx.bufferSubData(target, offset, null);
422 | }
423 | },
424 |
425 |
426 | Clear: (x) => {
427 | this.ctx.clear(x);
428 | },
429 | ClearColor: (r, g, b, a) => {
430 | this.ctx.clearColor(r, g, b, a);
431 | },
432 | ClearDepth: (x) => {
433 | this.ctx.clearDepth(x);
434 | },
435 | ClearStencil: (x) => {
436 | this.ctx.clearStencil(x);
437 | },
438 | ColorMask: (r, g, b, a) => {
439 | this.ctx.colorMask(!!r, !!g, !!b, !!a);
440 | },
441 | CompileShader: (shader) => {
442 | this.ctx.compileShader(this.shaders[shader]);
443 | },
444 |
445 |
446 | CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => {
447 | if (data) {
448 | this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize));
449 | } else {
450 | this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, null);
451 | }
452 | },
453 | CompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => {
454 | if (data) {
455 | this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, this.mem.loadBytes(data, imageSize));
456 | } else {
457 | this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null);
458 | }
459 | },
460 |
461 | CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => {
462 | this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border);
463 | },
464 | CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => {
465 | this.ctx.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
466 | },
467 |
468 |
469 | CreateBuffer: () => {
470 | let buffer = this.ctx.createBuffer();
471 | if (!buffer) {
472 | this.recordError(1282);
473 | return 0;
474 | }
475 | let id = this.getNewId(this.buffers);
476 | buffer.name = id
477 | this.buffers[id] = buffer;
478 | return id;
479 | },
480 | CreateFramebuffer: () => {
481 | let buffer = this.ctx.createFramebuffer();
482 | let id = this.getNewId(this.framebuffers);
483 | buffer.name = id
484 | this.framebuffers[id] = buffer;
485 | return id;
486 | },
487 | CreateProgram: () => {
488 | let program = this.ctx.createProgram();
489 | let id = this.getNewId(this.programs);
490 | program.name = id;
491 | this.programs[id] = program;
492 | return id;
493 | },
494 | CreateRenderbuffer: () => {
495 | let buffer = this.ctx.createRenderbuffer();
496 | let id = this.getNewId(this.renderbuffers);
497 | buffer.name = id;
498 | this.renderbuffers[id] = buffer;
499 | return id;
500 | },
501 | CreateShader: (shaderType) => {
502 | let shader = this.ctx.createShader(shaderType);
503 | let id = this.getNewId(this.shaders);
504 | shader.name = id;
505 | this.shaders[id] = shader;
506 | return id;
507 | },
508 | CreateTexture: () => {
509 | let texture = this.ctx.createTexture();
510 | if (!texture) {
511 | this.recordError(1282)
512 | return 0;
513 | }
514 | let id = this.getNewId(this.textures);
515 | texture.name = id;
516 | this.textures[id] = texture;
517 | return id;
518 | },
519 |
520 |
521 | CullFace: (mode) => {
522 | this.ctx.cullFace(mode);
523 | },
524 |
525 |
526 | DeleteBuffer: (id) => {
527 | let obj = this.buffers[id];
528 | if (obj && id != 0) {
529 | this.ctx.deleteBuffer(obj);
530 | this.buffers[id] = null;
531 | }
532 | },
533 | DeleteFramebuffer: (id) => {
534 | let obj = this.framebuffers[id];
535 | if (obj && id != 0) {
536 | this.ctx.deleteFramebuffer(obj);
537 | this.framebuffers[id] = null;
538 | }
539 | },
540 | DeleteProgram: (id) => {
541 | let obj = this.programs[id];
542 | if (obj && id != 0) {
543 | this.ctx.deleteProgram(obj);
544 | this.programs[id] = null;
545 | }
546 | },
547 | DeleteRenderbuffer: (id) => {
548 | let obj = this.renderbuffers[id];
549 | if (obj && id != 0) {
550 | this.ctx.deleteRenderbuffer(obj);
551 | this.renderbuffers[id] = null;
552 | }
553 | },
554 | DeleteShader: (id) => {
555 | let obj = this.shaders[id];
556 | if (obj && id != 0) {
557 | this.ctx.deleteShader(obj);
558 | this.shaders[id] = null;
559 | }
560 | },
561 | DeleteTexture: (id) => {
562 | let obj = this.textures[id];
563 | if (obj && id != 0) {
564 | this.ctx.deleteTexture(obj);
565 | this.textures[id] = null;
566 | }
567 | },
568 |
569 |
570 | DepthFunc: (func) => {
571 | this.ctx.depthFunc(func);
572 | },
573 | DepthMask: (flag) => {
574 | this.ctx.depthMask(!!flag);
575 | },
576 | DepthRange: (zNear, zFar) => {
577 | this.ctx.depthRange(zNear, zFar);
578 | },
579 | DetachShader: (program, shader) => {
580 | this.ctx.detachShader(this.programs[program], this.shaders[shader]);
581 | },
582 | Disable: (cap) => {
583 | this.ctx.disable(cap);
584 | },
585 | DisableVertexAttribArray: (index) => {
586 | this.ctx.disableVertexAttribArray(index);
587 | },
588 | DrawArrays: (mode, first, count) => {
589 | this.ctx.drawArrays(mode, first, count);
590 | },
591 | DrawElements: (mode, count, type, indices) => {
592 | this.ctx.drawElements(mode, count, type, indices);
593 | },
594 |
595 |
596 | Enable: (cap) => {
597 | this.ctx.enable(cap);
598 | },
599 | EnableVertexAttribArray: (index) => {
600 | this.ctx.enableVertexAttribArray(index);
601 | },
602 | Finish: () => {
603 | this.ctx.finish();
604 | },
605 | Flush: () => {
606 | this.ctx.flush();
607 | },
608 | FramebufferRenderbuffer: (target, attachment, renderbuffertarget, renderbuffer) => {
609 | this.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]);
610 | },
611 | FramebufferTexture2D: (target, attachment, textarget, texture, level) => {
612 | this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level);
613 | },
614 | FrontFace: (mode) => {
615 | this.ctx.frontFace(mode);
616 | },
617 |
618 |
619 | GenerateMipmap: (target) => {
620 | this.ctx.generateMipmap(target);
621 | },
622 |
623 |
624 | GetAttribLocation: (program, name_ptr, name_len) => {
625 | let name = this.mem.loadString(name_ptr, name_len);
626 | return this.ctx.getAttribLocation(this.programs[program], name);
627 | },
628 |
629 |
630 | GetParameter: (pname) => {
631 | return this.ctx.getParameter(pname);
632 | },
633 | GetProgramParameter: (program, pname) => {
634 | return this.ctx.getProgramParameter(this.programs[program], pname)
635 | },
636 | GetProgramInfoLog: (program, buf_ptr, buf_len, length_ptr) => {
637 | let log = this.ctx.getProgramInfoLog(this.programs[program]);
638 | if (log === null) {
639 | log = "(unknown error)";
640 | }
641 | if (buf_len > 0 && buf_ptr) {
642 | let n = Math.min(buf_len, log.length);
643 | log = log.substring(0, n);
644 | this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log))
645 |
646 | this.mem.storeInt(length_ptr, n);
647 | }
648 | },
649 | GetShaderInfoLog: (shader, buf_ptr, buf_len, length_ptr) => {
650 | let log = this.ctx.getShaderInfoLog(this.shaders[shader]);
651 | if (log === null) {
652 | log = "(unknown error)";
653 | }
654 | if (buf_len > 0 && buf_ptr) {
655 | let n = Math.min(buf_len, log.length);
656 | log = log.substring(0, n);
657 | this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log))
658 |
659 | this.mem.storeInt(length_ptr, n);
660 | }
661 | },
662 | GetShaderiv: (shader, pname, p) => {
663 | if (p) {
664 | if (pname == 35716) {
665 | let log = this.ctx.getShaderInfoLog(this.shaders[shader]);
666 | if (log === null) {
667 | log = "(unknown error)";
668 | }
669 | this.mem.storeInt(p, log.length+1);
670 | } else if (pname == 35720) {
671 | let source = this.ctx.getShaderSource(this.shaders[shader]);
672 | let sourceLength = (source === null || source.length == 0) ? 0 : source.length+1;
673 | this.mem.storeInt(p, sourceLength);
674 | } else {
675 | let param = this.ctx.getShaderParameter(this.shaders[shader], pname);
676 | this.mem.storeI32(p, param);
677 | }
678 | } else {
679 | this.recordError(1281);
680 | }
681 | },
682 |
683 |
684 | GetUniformLocation: (program, name_ptr, name_len) => {
685 | let name = this.mem.loadString(name_ptr, name_len);
686 | let arrayOffset = 0;
687 | if (name.indexOf("]", name.length - 1) !== -1) {
688 | let ls = name.lastIndexOf("["),
689 | arrayIndex = name.slice(ls + 1, -1);
690 | if (arrayIndex.length > 0 && (arrayOffset = parseInt(arrayIndex)) < 0) {
691 | return -1;
692 | }
693 | name = name.slice(0, ls)
694 | }
695 | var ptable = this.programInfos[program];
696 | if (!ptable) {
697 | return -1;
698 | }
699 | var uniformInfo = ptable.uniforms[name];
700 | return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1
701 | },
702 |
703 |
704 | GetVertexAttribOffset: (index, pname) => {
705 | return this.ctx.getVertexAttribOffset(index, pname);
706 | },
707 |
708 |
709 | Hint: (target, mode) => {
710 | this.ctx.hint(target, mode);
711 | },
712 |
713 |
714 | IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]),
715 | IsEnabled: (cap) => this.ctx.isEnabled(cap),
716 | IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]),
717 | IsProgram: (program) => this.ctx.isProgram(this.programs[program]),
718 | IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]),
719 | IsShader: (shader) => this.ctx.isShader(this.shaders[shader]),
720 | IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]),
721 |
722 | LineWidth: (width) => {
723 | this.ctx.lineWidth(width);
724 | },
725 | LinkProgram: (program) => {
726 | this.ctx.linkProgram(this.programs[program]);
727 | this.programInfos[program] = null;
728 | this.populateUniformTable(program);
729 | },
730 | PixelStorei: (pname, param) => {
731 | this.ctx.pixelStorei(pname, param);
732 | },
733 | PolygonOffset: (factor, units) => {
734 | this.ctx.polygonOffset(factor, units);
735 | },
736 |
737 |
738 | ReadnPixels: (x, y, width, height, format, type, bufSize, data) => {
739 | this.ctx.readPixels(x, y, width, height, format, type, this.mem.loadBytes(data, bufSize));
740 | },
741 | RenderbufferStorage: (target, internalformat, width, height) => {
742 | this.ctx.renderbufferStorage(target, internalformat, width, height);
743 | },
744 | SampleCoverage: (value, invert) => {
745 | this.ctx.sampleCoverage(value, !!invert);
746 | },
747 | Scissor: (x, y, width, height) => {
748 | this.ctx.scissor(x, y, width, height);
749 | },
750 | ShaderSource: (shader, strings_ptr, strings_length) => {
751 | let source = this.getSource(shader, strings_ptr, strings_length);
752 | this.ctx.shaderSource(this.shaders[shader], source);
753 | },
754 |
755 | StencilFunc: (func, ref, mask) => {
756 | this.ctx.stencilFunc(func, ref, mask);
757 | },
758 | StencilFuncSeparate: (face, func, ref, mask) => {
759 | this.ctx.stencilFuncSeparate(face, func, ref, mask);
760 | },
761 | StencilMask: (mask) => {
762 | this.ctx.stencilMask(mask);
763 | },
764 | StencilMaskSeparate: (face, mask) => {
765 | this.ctx.stencilMaskSeparate(face, mask);
766 | },
767 | StencilOp: (fail, zfail, zpass) => {
768 | this.ctx.stencilOp(fail, zfail, zpass);
769 | },
770 | StencilOpSeparate: (face, fail, zfail, zpass) => {
771 | this.ctx.stencilOpSeparate(face, fail, zfail, zpass);
772 | },
773 |
774 |
775 | TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => {
776 | if (data) {
777 | this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size));
778 | } else {
779 | this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, null);
780 | }
781 | },
782 | TexParameterf: (target, pname, param) => {
783 | this.ctx.texParameterf(target, pname, param);
784 | },
785 | TexParameteri: (target, pname, param) => {
786 | this.ctx.texParameteri(target, pname, param);
787 | },
788 | TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => {
789 | this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size));
790 | },
791 |
792 |
793 | Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); },
794 | Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); },
795 | Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); },
796 | Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); },
797 |
798 | Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); },
799 | Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); },
800 | Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); },
801 | Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); },
802 |
803 | UniformMatrix2fv: (location, addr) => {
804 | let array = this.mem.loadF32Array(addr, 2*2);
805 | this.ctx.uniformMatrix2fv(this.uniforms[location], false, array);
806 | },
807 | UniformMatrix3fv: (location, addr) => {
808 | let array = this.mem.loadF32Array(addr, 3*3);
809 | this.ctx.uniformMatrix3fv(this.uniforms[location], false, array);
810 | },
811 | UniformMatrix4fv: (location, addr) => {
812 | let array = this.mem.loadF32Array(addr, 4*4);
813 | this.ctx.uniformMatrix4fv(this.uniforms[location], false, array);
814 | },
815 |
816 | UseProgram: (program) => {
817 | if (program) this.ctx.useProgram(this.programs[program]);
818 | },
819 | ValidateProgram: (program) => {
820 | if (program) this.ctx.validateProgram(this.programs[program]);
821 | },
822 |
823 |
824 | VertexAttrib1f: (index, x) => {
825 | this.ctx.vertexAttrib1f(index, x);
826 | },
827 | VertexAttrib2f: (index, x, y) => {
828 | this.ctx.vertexAttrib2f(index, x, y);
829 | },
830 | VertexAttrib3f: (index, x, y, z) => {
831 | this.ctx.vertexAttrib3f(index, x, y, z);
832 | },
833 | VertexAttrib4f: (index, x, y, z, w) => {
834 | this.ctx.vertexAttrib4f(index, x, y, z, w);
835 | },
836 | VertexAttribPointer: (index, size, type, normalized, stride, ptr) => {
837 | this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr);
838 | },
839 |
840 | Viewport: (x, y, w, h) => {
841 | this.ctx.viewport(x, y, w, h);
842 | },
843 | };
844 | }
845 |
846 | getWebGL2Interface() {
847 | return {
848 | /* Buffer objects */
849 | CopyBufferSubData: (readTarget, writeTarget, readOffset, writeOffset, size) => {
850 | this.assertWebGL2();
851 | this.ctx.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
852 | },
853 | GetBufferSubData: (target, srcByteOffset, dst_buffer_ptr, dst_buffer_len, dstOffset, length) => {
854 | this.assertWebGL2();
855 | this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length);
856 | },
857 |
858 | /* Framebuffer objects */
859 | BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => {
860 | this.assertWebGL2();
861 | this.ctx.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
862 | },
863 | FramebufferTextureLayer: (target, attachment, texture, level, layer) => {
864 | this.assertWebGL2();
865 | this.ctx.framebufferTextureLayer(target, attachment, this.textures[texture], level, layer);
866 | },
867 | InvalidateFramebuffer: (target, attachments_ptr, attachments_len) => {
868 | this.assertWebGL2();
869 | let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len);
870 | this.ctx.invalidateFramebuffer(target, attachments);
871 | },
872 | InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => {
873 | this.assertWebGL2();
874 | let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len);
875 | this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height);
876 | },
877 | ReadBuffer: (src) => {
878 | this.assertWebGL2();
879 | this.ctx.readBuffer(src);
880 | },
881 |
882 | /* Renderbuffer objects */
883 | RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => {
884 | this.assertWebGL2();
885 | this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height);
886 | },
887 |
888 | /* Texture objects */
889 |
890 | TexStorage3D: (target, levels, internalformat, width, height, depth) => {
891 | this.assertWebGL2();
892 | this.ctx.texStorage3D(target, levels, internalformat, width, height, depth);
893 | },
894 | TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => {
895 | this.assertWebGL2();
896 | if (data) {
897 | this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, this.mem.loadBytes(data, size));
898 | } else {
899 | this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, null);
900 | }
901 | },
902 | TexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, size, data) => {
903 | this.assertWebGL2();
904 | this.ctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, this.mem.loadBytes(data, size));
905 | },
906 | CompressedTexImage3D: (target, level, internalformat, width, height, depth, border, imageSize, data) => {
907 | this.assertWebGL2();
908 | if (data) {
909 | this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, this.mem.loadBytes(data, imageSize));
910 | } else {
911 | this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, null);
912 | }
913 | },
914 | CompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => {
915 | this.assertWebGL2();
916 | if (data) {
917 | this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, this.mem.loadBytes(data, imageSize));
918 | } else {
919 | this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null);
920 | }
921 | },
922 |
923 | CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => {
924 | this.assertWebGL2();
925 | this.ctx.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height);
926 | },
927 |
928 | /* Programs and shaders */
929 | GetFragDataLocation: (program, name_ptr, name_len) => {
930 | this.assertWebGL2();
931 | return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len));
932 | },
933 |
934 | /* Uniforms */
935 | Uniform1ui: (location, v0) => {
936 | this.assertWebGL2();
937 | this.ctx.uniform1ui(this.uniforms[location], v0);
938 | },
939 | Uniform2ui: (location, v0, v1) => {
940 | this.assertWebGL2();
941 | this.ctx.uniform2ui(this.uniforms[location], v0, v1);
942 | },
943 | Uniform3ui: (location, v0, v1, v2) => {
944 | this.assertWebGL2();
945 | this.ctx.uniform3ui(this.uniforms[location], v0, v1, v2);
946 | },
947 | Uniform4ui: (location, v0, v1, v2, v3) => {
948 | this.assertWebGL2();
949 | this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3);
950 | },
951 |
952 | UniformMatrix3x2fv: (location, addr) => {
953 | this.assertWebGL2();
954 | let array = this.mem.loadF32Array(addr, 3*2);
955 | this.ctx.uniformMatrix3x2fv(this.uniforms[location], false, array);
956 | },
957 | UniformMatrix4x2fv: (location, addr) => {
958 | this.assertWebGL2();
959 | let array = this.mem.loadF32Array(addr, 4*2);
960 | this.ctx.uniformMatrix4x2fv(this.uniforms[location], false, array);
961 | },
962 | UniformMatrix2x3fv: (location, addr) => {
963 | this.assertWebGL2();
964 | let array = this.mem.loadF32Array(addr, 2*3);
965 | this.ctx.uniformMatrix2x3fv(this.uniforms[location], false, array);
966 | },
967 | UniformMatrix4x3fv: (location, addr) => {
968 | this.assertWebGL2();
969 | let array = this.mem.loadF32Array(addr, 4*3);
970 | this.ctx.uniformMatrix4x3fv(this.uniforms[location], false, array);
971 | },
972 | UniformMatrix2x4fv: (location, addr) => {
973 | this.assertWebGL2();
974 | let array = this.mem.loadF32Array(addr, 2*4);
975 | this.ctx.uniformMatrix2x4fv(this.uniforms[location], false, array);
976 | },
977 | UniformMatrix3x4fv: (location, addr) => {
978 | this.assertWebGL2();
979 | let array = this.mem.loadF32Array(addr, 3*4);
980 | this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array);
981 | },
982 |
983 | /* Vertex attribs */
984 | VertexAttribI4i: (index, x, y, z, w) => {
985 | this.assertWebGL2();
986 | this.ctx.vertexAttribI4i(index, x, y, z, w);
987 | },
988 | VertexAttribI4ui: (index, x, y, z, w) => {
989 | this.assertWebGL2();
990 | this.ctx.vertexAttribI4ui(index, x, y, z, w);
991 | },
992 | VertexAttribIPointer: (index, size, type, stride, offset) => {
993 | this.assertWebGL2();
994 | this.ctx.vertexAttribIPointer(index, size, type, stride, offset);
995 | },
996 |
997 | /* Writing to the drawing buffer */
998 | VertexAttribDivisor: (index, divisor) => {
999 | this.assertWebGL2();
1000 | this.ctx.vertexAttribDivisor(index, divisor);
1001 | },
1002 | DrawArraysInstanced: (mode, first, count, instanceCount) => {
1003 | this.assertWebGL2();
1004 | this.ctx.drawArraysInstanced(mode, first, count, instanceCount);
1005 | },
1006 | DrawElementsInstanced: (mode, count, type, offset, instanceCount) => {
1007 | this.assertWebGL2();
1008 | this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount);
1009 | },
1010 | DrawRangeElements: (mode, start, end, count, type, offset) => {
1011 | this.assertWebGL2();
1012 | this.ctx.drawRangeElements(mode, start, end, count, type, offset);
1013 | },
1014 |
1015 | /* Multiple Render Targets */
1016 | DrawBuffers: (buffers_ptr, buffers_len) => {
1017 | this.assertWebGL2();
1018 | let array = this.mem.loadU32Array(buffers_ptr, buffers_len);
1019 | this.ctx.drawBuffers(array);
1020 | },
1021 | ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => {
1022 | this.assertWebGL2();
1023 | let array = this.mem.loadF32Array(values_ptr, values_len);
1024 | this.ctx.clearBufferfv(buffer, drawbuffer, array);
1025 | },
1026 | ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => {
1027 | this.assertWebGL2();
1028 | let array = this.mem.loadI32Array(values_ptr, values_len);
1029 | this.ctx.clearBufferiv(buffer, drawbuffer, array);
1030 | },
1031 | ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => {
1032 | this.assertWebGL2();
1033 | let array = this.mem.loadU32Array(values_ptr, values_len);
1034 | this.ctx.clearBufferuiv(buffer, drawbuffer, array);
1035 | },
1036 | ClearBufferfi: (buffer, drawbuffer, depth, stencil) => {
1037 | this.assertWebGL2();
1038 | this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil);
1039 | },
1040 |
1041 | /* Query Objects */
1042 | CreateQuery: () => {
1043 | this.assertWebGL2();
1044 | let query = this.ctx.createQuery();
1045 | let id = this.getNewId(this.queries);
1046 | query.name = id;
1047 | this.queries[id] = query;
1048 | return id;
1049 | },
1050 | DeleteQuery: (id) => {
1051 | this.assertWebGL2();
1052 | let obj = this.queries[id];
1053 | if (obj && id != 0) {
1054 | this.ctx.deleteQuery(obj);
1055 | this.queries[id] = null;
1056 | }
1057 | },
1058 | IsQuery: (query) => {
1059 | this.assertWebGL2();
1060 | return this.ctx.isQuery(this.queries[query]);
1061 | },
1062 | BeginQuery: (target, query) => {
1063 | this.assertWebGL2();
1064 | this.ctx.beginQuery(target, this.queries[query])
1065 | },
1066 | EndQuery: (target) => {
1067 | this.assertWebGL2();
1068 | this.ctx.endQuery(target);
1069 | },
1070 | GetQuery: (target, pname) => {
1071 | this.assertWebGL2();
1072 | let query = this.ctx.getQuery(target, pname);
1073 | if (!query) {
1074 | return 0;
1075 | }
1076 | if (this.queries.indexOf(query) !== -1) {
1077 | return query.name;
1078 | }
1079 | let id = this.getNewId(this.queries);
1080 | query.name = id;
1081 | this.queries[id] = query;
1082 | return id;
1083 | },
1084 |
1085 | /* Sampler Objects */
1086 | CreateSampler: () => {
1087 | this.assertWebGL2();
1088 | let sampler = this.ctx.createSampler();
1089 | let id = this.getNewId(this.samplers);
1090 | sampler.name = id;
1091 | this.samplers[id] = sampler;
1092 | return id;
1093 | },
1094 | DeleteSampler: (id) => {
1095 | this.assertWebGL2();
1096 | let obj = this.samplers[id];
1097 | if (obj && id != 0) {
1098 | this.ctx.deleteSampler(obj);
1099 | this.samplers[id] = null;
1100 | }
1101 | },
1102 | IsSampler: (sampler) => {
1103 | this.assertWebGL2();
1104 | return this.ctx.isSampler(this.samplers[sampler]);
1105 | },
1106 | BindSampler: (unit, sampler) => {
1107 | this.assertWebGL2();
1108 | this.ctx.bindSampler(unit, this.samplers[sampler]);
1109 | },
1110 | SamplerParameteri: (sampler, pname, param) => {
1111 | this.assertWebGL2();
1112 | this.ctx.samplerParameteri(this.samplers[sampler], pname, param);
1113 | },
1114 | SamplerParameterf: (sampler, pname, param) => {
1115 | this.assertWebGL2();
1116 | this.ctx.samplerParameterf(this.samplers[sampler], pname, param);
1117 | },
1118 |
1119 | /* Sync objects */
1120 | FenceSync: (condition, flags) => {
1121 | this.assertWebGL2();
1122 | let sync = this.ctx.fenceSync(condition, flags);
1123 | let id = this.getNewId(this.syncs);
1124 | sync.name = id;
1125 | this.syncs[id] = sync;
1126 | return id;
1127 | },
1128 | IsSync: (sync) => {
1129 | this.assertWebGL2();
1130 | return this.ctx.isSync(this.syncs[sync]);
1131 | },
1132 | DeleteSync: (id) => {
1133 | this.assertWebGL2();
1134 | let obj = this.syncs[id];
1135 | if (obj && id != 0) {
1136 | this.ctx.deleteSampler(obj);
1137 | this.syncs[id] = null;
1138 | }
1139 | },
1140 | ClientWaitSync: (sync, flags, timeout) => {
1141 | this.assertWebGL2();
1142 | return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout);
1143 | },
1144 | WaitSync: (sync, flags, timeout) => {
1145 | this.assertWebGL2();
1146 | this.ctx.waitSync(this.syncs[sync], flags, timeout) ;
1147 | },
1148 |
1149 |
1150 | /* Transform Feedback */
1151 | CreateTransformFeedback: () => {
1152 | this.assertWebGL2();
1153 | let transformFeedback = this.ctx.createTransformFeedback();
1154 | let id = this.getNewId(this.transformFeedbacks);
1155 | transformFeedback.name = id;
1156 | this.transformFeedbacks[id] = transformFeedback;
1157 | return id;
1158 | },
1159 | DeleteTransformFeedback: (id) => {
1160 | this.assertWebGL2();
1161 | let obj = this.transformFeedbacks[id];
1162 | if (obj && id != 0) {
1163 | this.ctx.deleteTransformFeedback(obj);
1164 | this.transformFeedbacks[id] = null;
1165 | }
1166 | },
1167 | IsTransformFeedback: (tf) => {
1168 | this.assertWebGL2();
1169 | return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]);
1170 | },
1171 | BindTransformFeedback: (target, tf) => {
1172 | this.assertWebGL2();
1173 | this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]);
1174 | },
1175 | BeginTransformFeedback: (primitiveMode) => {
1176 | this.assertWebGL2();
1177 | this.ctx.beginTransformFeedback(primitiveMode);
1178 | },
1179 | EndTransformFeedback: () => {
1180 | this.assertWebGL2();
1181 | this.ctx.endTransformFeedback();
1182 | },
1183 | TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => {
1184 | this.assertWebGL2();
1185 | const stringSize = this.mem.intSize*2;
1186 | let varyings = [];
1187 | for (let i = 0; i < varyings_len; i++) {
1188 | let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4);
1189 | let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 1*4);
1190 | varyings.push(this.mem.loadString(ptr, len));
1191 | }
1192 | this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode);
1193 | },
1194 | PauseTransformFeedback: () => {
1195 | this.assertWebGL2();
1196 | this.ctx.pauseTransformFeedback();
1197 | },
1198 | ResumeTransformFeedback: () => {
1199 | this.assertWebGL2();
1200 | this.ctx.resumeTransformFeedback();
1201 | },
1202 |
1203 |
1204 | /* Uniform Buffer Objects and Transform Feedback Buffers */
1205 | BindBufferBase: (target, index, buffer) => {
1206 | this.assertWebGL2();
1207 | this.ctx.bindBufferBase(target, index, this.buffers[buffer]);
1208 | },
1209 | BindBufferRange: (target, index, buffer, offset, size) => {
1210 | this.assertWebGL2();
1211 | this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size);
1212 | },
1213 | GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => {
1214 | this.assertWebGL2();
1215 | return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len));
1216 | },
1217 | // any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname);
1218 | GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => {
1219 | this.assertWebGL2();
1220 | let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex);
1221 |
1222 | let n = Math.min(buf_len, name.length);
1223 | name = name.substring(0, n);
1224 | this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(name))
1225 | this.mem.storeInt(length_ptr, n);
1226 | },
1227 | UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => {
1228 | this.assertWebGL2();
1229 | this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding);
1230 | },
1231 |
1232 | /* Vertex Array Objects */
1233 | CreateVertexArray: () => {
1234 | this.assertWebGL2();
1235 | let vao = this.ctx.createVertexArray();
1236 | let id = this.getNewId(this.vaos);
1237 | vao.name = id;
1238 | this.vaos[id] = vao;
1239 | return id;
1240 | },
1241 | DeleteVertexArray: (id) => {
1242 | this.assertWebGL2();
1243 | let obj = this.vaos[id];
1244 | if (obj && id != 0) {
1245 | this.ctx.deleteVertexArray(obj);
1246 | this.vaos[id] = null;
1247 | }
1248 | },
1249 | IsVertexArray: (vertexArray) => {
1250 | this.assertWebGL2();
1251 | return this.ctx.isVertexArray(this.vaos[vertexArray]);
1252 | },
1253 | BindVertexArray: (vertexArray) => {
1254 | this.assertWebGL2();
1255 | this.ctx.bindVertexArray(this.vaos[vertexArray]);
1256 | },
1257 | };
1258 | }
1259 | };
1260 |
1261 |
1262 | function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, eventQueue, event_temp) {
1263 | const MAX_INFO_CONSOLE_LINES = 512;
1264 | let infoConsoleLines = new Array();
1265 | let currentLine = {};
1266 | currentLine[false] = "";
1267 | currentLine[true] = "";
1268 | let prevIsError = false;
1269 |
1270 | const writeToConsole = (line, isError) => {
1271 | if (!line) {
1272 | return;
1273 | }
1274 |
1275 | const println = (text, forceIsError) => {
1276 | let style = [
1277 | "color: #eee",
1278 | "background-color: #d20",
1279 | "padding: 2px 4px",
1280 | "border-radius: 2px",
1281 | ].join(";");
1282 | let doIsError = isError;
1283 | if (forceIsError !== undefined) {
1284 | doIsError = forceIsError;
1285 | }
1286 |
1287 | if (doIsError) {
1288 | console.log("%c"+text, style);
1289 | } else {
1290 | console.log(text);
1291 | }
1292 |
1293 | };
1294 |
1295 | // Print to console
1296 | if (line == "\n") {
1297 | println(currentLine[isError]);
1298 | currentLine[isError] = "";
1299 | } else if (!line.includes("\n")) {
1300 | currentLine[isError] = currentLine[isError].concat(line);
1301 | } else {
1302 | let lines = line.split("\n");
1303 | let printLast = lines.length > 1 && line.endsWith("\n");
1304 | println(currentLine[isError].concat(lines[0]));
1305 | currentLine[isError] = "";
1306 | for (let i = 1; i < lines.length-1; i++) {
1307 | println(lines[i]);
1308 | }
1309 | let last = lines[lines.length-1];
1310 | if (printLast) {
1311 | println(last);
1312 | } else {
1313 | currentLine[isError] = last;
1314 | }
1315 | }
1316 |
1317 | if (prevIsError != isError) {
1318 | if (prevIsError) {
1319 | println(currentLine[prevIsError], prevIsError);
1320 | currentLine[prevIsError] = "";
1321 | }
1322 | }
1323 | prevIsError = isError;
1324 |
1325 |
1326 | // HTML based console
1327 | if (!consoleElement) {
1328 | return;
1329 | }
1330 | const wrap = (x) => {
1331 | if (isError) {
1332 | return ''+x+'';
1333 | }
1334 | return x;
1335 | };
1336 |
1337 | if (line == "\n") {
1338 | infoConsoleLines.push(line);
1339 | } else if (!line.includes("\n")) {
1340 | let prevLine = "";
1341 | if (infoConsoleLines.length > 0) {
1342 | prevLine = infoConsoleLines.pop();
1343 | }
1344 | infoConsoleLines.push(prevLine.concat(wrap(line)));
1345 | } else {
1346 | let lines = line.split("\n");
1347 | let lastHasNewline = lines.length > 1 && line.endsWith("\n");
1348 |
1349 | let prevLine = "";
1350 | if (infoConsoleLines.length > 0) {
1351 | prevLine = infoConsoleLines.pop();
1352 | }
1353 | infoConsoleLines.push(prevLine.concat(wrap(lines[0]).concat("\n")));
1354 |
1355 | for (let i = 1; i < lines.length-1; i++) {
1356 | infoConsoleLines.push(wrap(lines[i]).concat("\n"));
1357 | }
1358 | let last = lines[lines.length-1];
1359 | if (lastHasNewline) {
1360 | infoConsoleLines.push(last.concat("\n"));
1361 | } else {
1362 | infoConsoleLines.push(last);
1363 | }
1364 | }
1365 |
1366 | if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) {
1367 | infoConsoleLines.shift(MAX_INFO_CONSOLE_LINES);
1368 | }
1369 |
1370 | let data = "";
1371 | for (let i = 0; i < infoConsoleLines.length; i++) {
1372 | data = data.concat(infoConsoleLines[i]);
1373 | }
1374 |
1375 | let info = consoleElement;
1376 | info.innerHTML = data;
1377 | info.scrollTop = info.scrollHeight;
1378 | };
1379 |
1380 | let webglContext = new WebGLInterface(wasmMemoryInterface);
1381 |
1382 | const env = {};
1383 |
1384 | if (memory) {
1385 | env.memory = memory;
1386 | }
1387 |
1388 | return {
1389 | env,
1390 | "odin_env": {
1391 | write: (fd, ptr, len) => {
1392 | const str = wasmMemoryInterface.loadString(ptr, len);
1393 | if (fd == 1) {
1394 | writeToConsole(str, false);
1395 | return;
1396 | } else if (fd == 2) {
1397 | writeToConsole(str, true);
1398 | return;
1399 | } else {
1400 | throw new Error("Invalid fd to 'write'" + stripNewline(str));
1401 | }
1402 | },
1403 | trap: () => { throw new Error() },
1404 | alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) },
1405 | abort: () => { Module.abort() },
1406 | evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); },
1407 |
1408 | // return a bigint to be converted to i64
1409 | time_now: () => BigInt(Date.now()),
1410 | tick_now: () => performance.now(),
1411 | time_sleep: (duration_ms) => {
1412 | if (duration_ms > 0) {
1413 | // TODO(bill): Does this even make any sense?
1414 | }
1415 | },
1416 |
1417 | sqrt: Math.sqrt,
1418 | sin: Math.sin,
1419 | cos: Math.cos,
1420 | pow: Math.pow,
1421 | fmuladd: (x, y, z) => x*y + z,
1422 | ln: Math.log,
1423 | exp: Math.exp,
1424 | ldexp: (x, exp) => x * Math.pow(2, exp),
1425 |
1426 | rand_bytes: (ptr, len) => {
1427 | const view = new Uint8Array(wasmMemoryInterface.memory.buffer, ptr, len)
1428 | crypto.getRandomValues(view)
1429 | },
1430 | },
1431 | "odin_dom": {
1432 | init_event_raw: (ep) => {
1433 | const W = wasmMemoryInterface.intSize;
1434 | let offset = ep;
1435 | let off = (amount, alignment) => {
1436 | if (alignment === undefined) {
1437 | alignment = Math.min(amount, W);
1438 | }
1439 | if (offset % alignment != 0) {
1440 | offset += alignment - (offset%alignment);
1441 | }
1442 | let x = offset;
1443 | offset += amount;
1444 | return x;
1445 | };
1446 |
1447 | let align = (alignment) => {
1448 | const modulo = offset & (alignment-1);
1449 | if (modulo != 0) {
1450 | offset += alignment - modulo
1451 | }
1452 | };
1453 |
1454 | let wmi = wasmMemoryInterface;
1455 |
1456 | if (!event_temp.data) {
1457 | return;
1458 | }
1459 |
1460 | let e = event_temp.data.event;
1461 |
1462 | wmi.storeU32(off(4), event_temp.data.name_code);
1463 | if (e.target == document) {
1464 | wmi.storeU32(off(4), 1);
1465 | } else if (e.target == window) {
1466 | wmi.storeU32(off(4), 2);
1467 | } else {
1468 | wmi.storeU32(off(4), 0);
1469 | }
1470 | if (e.currentTarget == document) {
1471 | wmi.storeU32(off(4), 1);
1472 | } else if (e.currentTarget == window) {
1473 | wmi.storeU32(off(4), 2);
1474 | } else {
1475 | wmi.storeU32(off(4), 0);
1476 | }
1477 |
1478 | align(W);
1479 |
1480 | wmi.storeI32(off(W), event_temp.data.id_ptr);
1481 | wmi.storeUint(off(W), event_temp.data.id_len);
1482 |
1483 | align(8);
1484 | wmi.storeF64(off(8), e.timeStamp*1e-3);
1485 |
1486 | wmi.storeU8(off(1), e.eventPhase);
1487 | let options = 0;
1488 | if (!!e.bubbles) { options |= 1<<0; }
1489 | if (!!e.cancelable) { options |= 1<<1; }
1490 | if (!!e.composed) { options |= 1<<2; }
1491 | wmi.storeU8(off(1), options);
1492 | wmi.storeU8(off(1), !!e.isComposing);
1493 | wmi.storeU8(off(1), !!e.isTrusted);
1494 |
1495 | align(8);
1496 | if (e instanceof WheelEvent) {
1497 | wmi.storeF64(off(8), e.deltaX);
1498 | wmi.storeF64(off(8), e.deltaY);
1499 | wmi.storeF64(off(8), e.deltaZ);
1500 | wmi.storeU32(off(4), e.deltaMode);
1501 | } else if (e instanceof MouseEvent) {
1502 | wmi.storeI64(off(8), e.screenX);
1503 | wmi.storeI64(off(8), e.screenY);
1504 | wmi.storeI64(off(8), e.clientX);
1505 | wmi.storeI64(off(8), e.clientY);
1506 | wmi.storeI64(off(8), e.offsetX);
1507 | wmi.storeI64(off(8), e.offsetY);
1508 | wmi.storeI64(off(8), e.pageX);
1509 | wmi.storeI64(off(8), e.pageY);
1510 | wmi.storeI64(off(8), e.movementX);
1511 | wmi.storeI64(off(8), e.movementY);
1512 |
1513 | wmi.storeU8(off(1), !!e.ctrlKey);
1514 | wmi.storeU8(off(1), !!e.shiftKey);
1515 | wmi.storeU8(off(1), !!e.altKey);
1516 | wmi.storeU8(off(1), !!e.metaKey);
1517 |
1518 | wmi.storeI16(off(2), e.button);
1519 | wmi.storeU16(off(2), e.buttons);
1520 | } else if (e instanceof KeyboardEvent) {
1521 | // Note: those strings are constructed
1522 | // on the native side from buffers that
1523 | // are filled later, so skip them
1524 | const keyPtr = off(W*2, W);
1525 | const codePtr = off(W*2, W);
1526 |
1527 | wmi.storeU8(off(1), e.location);
1528 |
1529 | wmi.storeU8(off(1), !!e.ctrlKey);
1530 | wmi.storeU8(off(1), !!e.shiftKey);
1531 | wmi.storeU8(off(1), !!e.altKey);
1532 | wmi.storeU8(off(1), !!e.metaKey);
1533 |
1534 | wmi.storeU8(off(1), !!e.repeat);
1535 |
1536 | wmi.storeInt(off(W, W), e.key.length)
1537 | wmi.storeInt(off(W, W), e.code.length)
1538 | wmi.storeString(off(16, 1), e.key);
1539 | wmi.storeString(off(16, 1), e.code);
1540 | } else if (e.type === 'scroll') {
1541 | wmi.storeF64(off(8, 8), window.scrollX);
1542 | wmi.storeF64(off(8, 8), window.scrollY);
1543 | } else if (e.type === 'visibilitychange') {
1544 | wmi.storeU8(off(1), !document.hidden);
1545 | } else if (e instanceof GamepadEvent) {
1546 | const idPtr = off(W*2, W);
1547 | const mappingPtr = off(W*2, W);
1548 |
1549 | wmi.storeI32(off(W, W), e.gamepad.index);
1550 | wmi.storeU8(off(1), !!e.gamepad.connected);
1551 | wmi.storeF64(off(8, 8), e.gamepad.timestamp);
1552 |
1553 | wmi.storeInt(off(W, W), e.gamepad.buttons.length);
1554 | wmi.storeInt(off(W, W), e.gamepad.axes.length);
1555 |
1556 | for (let i = 0; i < 64; i++) {
1557 | if (i < e.gamepad.buttons.length) {
1558 | let b = e.gamepad.buttons[i];
1559 | wmi.storeF64(off(8, 8), b.value);
1560 | wmi.storeU8(off(1), !!b.pressed);
1561 | wmi.storeU8(off(1), !!b.touched);
1562 | } else {
1563 | off(16, 8);
1564 | }
1565 | }
1566 | for (let i = 0; i < 16; i++) {
1567 | if (i < e.gamepad.axes.length) {
1568 | let a = e.gamepad.axes[i];
1569 | wmi.storeF64(off(8, 8), a);
1570 | } else {
1571 | off(8, 8);
1572 | }
1573 | }
1574 |
1575 | wmi.storeInt(off(W, W), e.gamepad.id.length)
1576 | wmi.storeInt(off(W, W), e.gamepad.mapping.length)
1577 | wmi.storeString(off(64, 1), e.gamepad.id);
1578 | wmi.storeString(off(64, 1), e.gamepad.mapping);
1579 | }
1580 | },
1581 |
1582 | add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => {
1583 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1584 | let name = wasmMemoryInterface.loadString(name_ptr, name_len);
1585 | let element = getElement(id);
1586 | if (element == undefined) {
1587 | return false;
1588 | }
1589 |
1590 | let listener = (e) => {
1591 | let event_data = {};
1592 | event_data.id_ptr = id_ptr;
1593 | event_data.id_len = id_len;
1594 | event_data.event = e;
1595 | event_data.name_code = name_code;
1596 |
1597 | eventQueue.push({event_data: event_data, data: data, callback: callback});
1598 | };
1599 | wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
1600 | element.addEventListener(name, listener, !!use_capture);
1601 | return true;
1602 | },
1603 |
1604 | add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => {
1605 | let name = wasmMemoryInterface.loadString(name_ptr, name_len);
1606 | let element = window;
1607 | let listener = (e) => {
1608 | let event_data = {};
1609 | event_data.id_ptr = 0;
1610 | event_data.id_len = 0;
1611 | event_data.event = e;
1612 | event_data.name_code = name_code;
1613 |
1614 | eventQueue.push({event_data: event_data, data: data, callback: callback});
1615 | };
1616 | wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
1617 | element.addEventListener(name, listener, !!use_capture);
1618 | return true;
1619 | },
1620 |
1621 | remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => {
1622 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1623 | let name = wasmMemoryInterface.loadString(name_ptr, name_len);
1624 | let element = getElement(id);
1625 | if (element == undefined) {
1626 | return false;
1627 | }
1628 |
1629 | let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}];
1630 | if (listener == undefined) {
1631 | return false;
1632 | }
1633 | element.removeEventListener(name, listener);
1634 | return true;
1635 | },
1636 | remove_window_event_listener: (name_ptr, name_len, data, callback) => {
1637 | let name = wasmMemoryInterface.loadString(name_ptr, name_len);
1638 | let element = window;
1639 | let key = {data: data, callback: callback};
1640 | let listener = wasmMemoryInterface.listenerMap[key];
1641 | if (!listener) {
1642 | return false;
1643 | }
1644 | wasmMemoryInterface.listenerMap[key] = undefined;
1645 |
1646 | element.removeEventListener(name, listener);
1647 | return true;
1648 | },
1649 |
1650 | event_stop_propagation: () => {
1651 | if (event_temp.data && event_temp.data.event) {
1652 | event_temp.data.event.stopPropagation();
1653 | }
1654 | },
1655 | event_stop_immediate_propagation: () => {
1656 | if (event_temp.data && event_temp.data.event) {
1657 | event_temp.data.event.stopImmediatePropagation();
1658 | }
1659 | },
1660 | event_prevent_default: () => {
1661 | if (event_temp.data && event_temp.data.event) {
1662 | event_temp.data.event.preventDefault();
1663 | }
1664 | },
1665 |
1666 | dispatch_custom_event: (id_ptr, id_len, name_ptr, name_len, options_bits) => {
1667 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1668 | let name = wasmMemoryInterface.loadString(name_ptr, name_len);
1669 | let options = {
1670 | bubbles: (options_bits & (1<<0)) !== 0,
1671 | cancelable: (options_bits & (1<<1)) !== 0,
1672 | composed: (options_bits & (1<<2)) !== 0,
1673 | };
1674 |
1675 | let element = getElement(id);
1676 | if (element) {
1677 | element.dispatchEvent(new Event(name, options));
1678 | return true;
1679 | }
1680 | return false;
1681 | },
1682 |
1683 | get_gamepad_state: (gamepad_id, ep) => {
1684 | let index = gamepad_id;
1685 | let gps = navigator.getGamepads();
1686 | if (0 <= index && index < gps.length) {
1687 | let gamepad = gps[index];
1688 | if (!gamepad) {
1689 | return false;
1690 | }
1691 |
1692 | const W = wasmMemoryInterface.intSize;
1693 | let offset = ep;
1694 | let off = (amount, alignment) => {
1695 | if (alignment === undefined) {
1696 | alignment = Math.min(amount, W);
1697 | }
1698 | if (offset % alignment != 0) {
1699 | offset += alignment - (offset%alignment);
1700 | }
1701 | let x = offset;
1702 | offset += amount;
1703 | return x;
1704 | };
1705 |
1706 | let align = (alignment) => {
1707 | const modulo = offset & (alignment-1);
1708 | if (modulo != 0) {
1709 | offset += alignment - modulo
1710 | }
1711 | };
1712 |
1713 | let wmi = wasmMemoryInterface;
1714 |
1715 | const idPtr = off(W*2, W);
1716 | const mappingPtr = off(W*2, W);
1717 |
1718 | wmi.storeI32(off(W), gamepad.index);
1719 | wmi.storeU8(off(1), !!gamepad.connected);
1720 | wmi.storeF64(off(8), gamepad.timestamp);
1721 |
1722 | wmi.storeInt(off(W), gamepad.buttons.length);
1723 | wmi.storeInt(off(W), gamepad.axes.length);
1724 |
1725 | for (let i = 0; i < 64; i++) {
1726 | if (i < gamepad.buttons.length) {
1727 | let b = gamepad.buttons[i];
1728 | wmi.storeF64(off(8, 8), b.value);
1729 | wmi.storeU8(off(1), !!b.pressed);
1730 | wmi.storeU8(off(1), !!b.touched);
1731 | } else {
1732 | off(16, 8);
1733 | }
1734 | }
1735 | for (let i = 0; i < 16; i++) {
1736 | if (i < gamepad.axes.length) {
1737 | wmi.storeF64(off(8, 8), gamepad.axes[i]);
1738 | } else {
1739 | off(8, 8);
1740 | }
1741 | }
1742 |
1743 | wmi.storeInt(off(W, W), gamepad.id.length)
1744 | wmi.storeInt(off(W, W), gamepad.mapping.length)
1745 | wmi.storeString(off(64, 1), gamepad.id);
1746 | wmi.storeString(off(64, 1), gamepad.mapping);
1747 |
1748 | return true;
1749 | }
1750 | return false;
1751 | },
1752 |
1753 | get_element_value_f64: (id_ptr, id_len) => {
1754 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1755 | let element = getElement(id);
1756 | return element ? element.value : 0;
1757 | },
1758 | get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => {
1759 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1760 | let element = getElement(id);
1761 | if (element) {
1762 | let str = element.value;
1763 | if (buf_len > 0 && buf_ptr) {
1764 | let n = Math.min(buf_len, str.length);
1765 | str = str.substring(0, n);
1766 | this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
1767 | return n;
1768 | }
1769 | }
1770 | return 0;
1771 | },
1772 | get_element_value_string_length: (id_ptr, id_len) => {
1773 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1774 | let element = getElement(id);
1775 | if (element) {
1776 | return element.value.length;
1777 | }
1778 | return 0;
1779 | },
1780 | get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => {
1781 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1782 | let element = getElement(id);
1783 | if (element) {
1784 | let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2);
1785 | values[0] = element.min;
1786 | values[1] = element.max;
1787 | }
1788 | },
1789 | set_element_value_f64: (id_ptr, id_len, value) => {
1790 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1791 | let element = getElement(id);
1792 | if (element) {
1793 | element.value = value;
1794 | }
1795 | },
1796 | set_element_value_string: (id_ptr, id_len, value_ptr, value_len) => {
1797 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1798 | let value = wasmMemoryInterface.loadString(value_ptr, value_len);
1799 | let element = getElement(id);
1800 | if (element) {
1801 | element.value = value;
1802 | }
1803 | },
1804 |
1805 | get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => {
1806 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1807 | let key = wasmMemoryInterface.loadString(key_ptr, key_len);
1808 | let element = getElement(id);
1809 | return element ? element[key] : 0;
1810 | },
1811 | get_element_key_string: (id_ptr, id_len, key_ptr, key_len, buf_ptr, buf_len) => {
1812 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1813 | let key = wasmMemoryInterface.loadString(key_ptr, key_len);
1814 | let element = getElement(id);
1815 | if (element) {
1816 | let str = element[key];
1817 | if (buf_len > 0 && buf_ptr) {
1818 | let n = Math.min(buf_len, str.length);
1819 | str = str.substring(0, n);
1820 | this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
1821 | return n;
1822 | }
1823 | }
1824 | return 0;
1825 | },
1826 | get_element_key_string_length: (id_ptr, id_len, key_ptr, key_len) => {
1827 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1828 | let key = wasmMemoryInterface.loadString(key_ptr, key_len);
1829 | let element = getElement(id);
1830 | if (element && element[key]) {
1831 | return element[key].length;
1832 | }
1833 | return 0;
1834 | },
1835 |
1836 | set_element_key_f64: (id_ptr, id_len, key_ptr, key_len, value) => {
1837 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1838 | let key = wasmMemoryInterface.loadString(key_ptr, key_len);
1839 | let element = getElement(id);
1840 | if (element) {
1841 | element[key] = value;
1842 | }
1843 | },
1844 | set_element_key_string: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => {
1845 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1846 | let key = wasmMemoryInterface.loadString(key_ptr, key_len);
1847 | let value = wasmMemoryInterface.loadString(value_ptr, value_len);
1848 | let element = getElement(id);
1849 | if (element) {
1850 | element[key] = value;
1851 | }
1852 | },
1853 |
1854 |
1855 | get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => {
1856 | let id = wasmMemoryInterface.loadString(id_ptr, id_len);
1857 | let element = getElement(id);
1858 | if (element) {
1859 | let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4);
1860 | let rect = element.getBoundingClientRect();
1861 | values[0] = rect.left;
1862 | values[1] = rect.top;
1863 | values[2] = rect.right - rect.left;
1864 | values[3] = rect.bottom - rect.top;
1865 | }
1866 | },
1867 | window_get_rect: (rect_ptr) => {
1868 | let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4);
1869 | values[0] = window.screenX;
1870 | values[1] = window.screenY;
1871 | values[2] = window.screen.width;
1872 | values[3] = window.screen.height;
1873 | },
1874 |
1875 | window_get_scroll: (pos_ptr) => {
1876 | let values = wasmMemoryInterface.loadF64Array(pos_ptr, 2);
1877 | values[0] = window.scrollX;
1878 | values[1] = window.scrollY;
1879 | },
1880 | window_set_scroll: (x, y) => {
1881 | window.scroll(x, y);
1882 | },
1883 |
1884 | device_pixel_ratio: () => {
1885 | return window.devicePixelRatio;
1886 | },
1887 |
1888 | },
1889 |
1890 | "webgl": webglContext.getWebGL1Interface(),
1891 | "webgl2": webglContext.getWebGL2Interface(),
1892 | };
1893 | };
1894 |
1895 | /**
1896 | * @param {string} wasmPath - Path to the WASM module to run
1897 | * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console
1898 | * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module
1899 | * @param {?WasmMemoryInterface} wasmMemoryInterface - Optional memory to use instead of the defaults
1900 | * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32`
1901 | */
1902 | async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemoryInterface, intSize = 4) {
1903 | if (!wasmMemoryInterface) {
1904 | wasmMemoryInterface = new WasmMemoryInterface();
1905 | }
1906 | wasmMemoryInterface.setIntSize(intSize);
1907 |
1908 | let eventQueue = new Array();
1909 | let event_temp = {};
1910 |
1911 | let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory, eventQueue, event_temp);
1912 | let exports = {};
1913 |
1914 | if (extraForeignImports !== undefined) {
1915 | imports = {
1916 | ...imports,
1917 | ...extraForeignImports,
1918 | };
1919 | }
1920 |
1921 | const response = await fetch(wasmPath);
1922 | const file = await response.arrayBuffer();
1923 | const wasm = await WebAssembly.instantiate(file, imports);
1924 | exports = wasm.instance.exports;
1925 | wasmMemoryInterface.setExports(exports);
1926 |
1927 | if (exports.memory) {
1928 | if (wasmMemoryInterface.memory) {
1929 | console.warn("WASM module exports memory, but `runWasm` was given an interface with existing memory too");
1930 | }
1931 | wasmMemoryInterface.setMemory(exports.memory);
1932 | }
1933 |
1934 | exports._start();
1935 |
1936 | // Define a `@export step :: proc(curr_time_stamp: f64) -> (keep_going: bool) {`
1937 | // in your app and it will get called every frame.
1938 | // return `false` to stop the execution of the module.
1939 | if (exports.step) {
1940 | const odin_ctx = exports.default_context_ptr();
1941 |
1942 | let prevTimeStamp = undefined;
1943 | function step(currTimeStamp) {
1944 | if (prevTimeStamp == undefined) {
1945 | prevTimeStamp = currTimeStamp;
1946 | }
1947 |
1948 | const dt = (currTimeStamp - prevTimeStamp)*0.001;
1949 | prevTimeStamp = currTimeStamp;
1950 |
1951 | while (eventQueue.length > 0) {
1952 | let e = eventQueue.shift()
1953 | event_temp.data = e.event_data;
1954 | exports.odin_dom_do_event_callback(e.data, e.callback, odin_ctx);
1955 | }
1956 | event_temp.data = null;
1957 |
1958 | if (!exports.step(dt, odin_ctx)) {
1959 | exports._end();
1960 | return;
1961 | }
1962 |
1963 | window.requestAnimationFrame(step);
1964 | }
1965 |
1966 | window.requestAnimationFrame(step);
1967 | } else {
1968 | exports._end();
1969 | }
1970 |
1971 | return;
1972 | };
1973 |
1974 | window.odin = {
1975 | // Interface Types
1976 | WasmMemoryInterface: WasmMemoryInterface,
1977 | WebGLInterface: WebGLInterface,
1978 |
1979 | // Functions
1980 | setupDefaultImports: odinSetupDefaultImports,
1981 | runWasm: runWasm,
1982 | };
1983 | })();
1984 |
--------------------------------------------------------------------------------