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