#+build js #+private file package karl2d @(private="package") RENDER_BACKEND_INTERFACE_WEBGL :: Render_Backend_Interface { state_size = webgl_state_size, init = webgl_init, shutdown = webgl_shutdown, clear = webgl_clear, present = webgl_present, draw = webgl_draw, resize_swapchain = webgl_resize_swapchain, get_swapchain_width = webgl_get_swapchain_width, get_swapchain_height = webgl_get_swapchain_height, flip_z = webgl_flip_z, set_internal_state = webgl_set_internal_state, create_texture = webgl_create_texture, load_texture = webgl_load_texture, update_texture = webgl_update_texture, destroy_texture = webgl_destroy_texture, create_render_texture = webgl_create_render_texture, destroy_render_target = webgl_destroy_render_target, set_texture_filter = webgl_set_texture_filter, load_shader = webgl_load_shader, destroy_shader = webgl_destroy_shader, default_shader_vertex_source = webgl_default_shader_vertex_source, default_shader_fragment_source = webgl_default_shader_fragment_source, } import "base:runtime" import gl "vendor:wasm/WebGL" import hm "handle_map" import "core:log" import "core:strings" import la "core:math/linalg" _ :: la WebGL_State :: struct { canvas_id: string, width: int, height: int, allocator: runtime.Allocator, shaders: hm.Handle_Map(WebGL_Shader, Shader_Handle, 1024*10), vertex_buffer_gpu: gl.Buffer, textures: hm.Handle_Map(WebGL_Texture, Texture_Handle, 1024*10), } WebGL_Shader_Constant_Buffer :: struct { buffer: gl.Buffer, size: int, block_index: i32, } WebGL_Shader_Constant_Type :: enum { Uniform, Block_Variable, } // OpenGL can have constants both in blocks (like constant buffers in D3D11), or as stand-alone // uniforms. We support both. WebGL_Shader_Constant :: struct { type: WebGL_Shader_Constant_Type, // if type is Uniform, then this is the uniform loc // if type is Block_Variable, then this is the block loc loc: i32, // if this is a block variable, then this is the offset to it block_variable_offset: u32, // if type is Uniform, then this contains the GL type of the uniform uniform_type: gl.Enum, } WebGL_Texture :: struct { handle: Texture_Handle, id: gl.Texture, format: Pixel_Format, } WebGL_Texture_Binding :: struct { loc: i32, } WebGL_Shader :: struct { handle: Shader_Handle, // This is like the "input layout" vao: gl.VertexArrayObject, program: gl.Program, constant_buffers: []WebGL_Shader_Constant_Buffer, constants: []WebGL_Shader_Constant, texture_bindings: []WebGL_Texture_Binding, } s: ^WebGL_State webgl_state_size :: proc() -> int { return size_of(WebGL_State) } webgl_init :: proc(state: rawptr, window_handle: Window_Handle, swapchain_width, swapchain_height: int, allocator := context.allocator) { s = (^WebGL_State)(state) canvas_id := (^HTML_Canvas_ID)(window_handle)^ s.canvas_id = strings.clone(canvas_id, allocator) s.width = swapchain_width s.height = swapchain_height s.allocator = allocator context_ok := gl.CreateCurrentContextById(s.canvas_id, gl.DEFAULT_CONTEXT_ATTRIBUTES) log.ensuref(context_ok, "Could not create context for canvas ID %s", s.canvas_id) set_context_ok := gl.SetCurrentContextById(s.canvas_id) log.ensuref(set_context_ok, "Failed setting context with canvas ID %s", s.canvas_id) gl.Enable(gl.DEPTH_TEST) gl.DepthFunc(gl.GREATER) s.vertex_buffer_gpu = gl.CreateBuffer() gl.BindBuffer(gl.ARRAY_BUFFER, s.vertex_buffer_gpu) gl.BufferData(gl.ARRAY_BUFFER, VERTEX_BUFFER_MAX, nil, gl.STREAM_DRAW) gl.Enable(gl.BLEND) gl.Viewport(0, 0, i32(s.width), i32(s.height)) } webgl_shutdown :: proc() { gl.DeleteBuffer(s.vertex_buffer_gpu) } webgl_clear :: proc(render_texture: Render_Target_Handle, color: Color) { c := f32_color_from_color(color) gl.ClearColor(c.r, c.g, c.b, c.a) gl.ClearDepth(-1) gl.Clear(u32(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)) } webgl_present :: proc() { // The browser flips the backbuffer for you when 'step' ends } webgl_draw :: proc( shd: Shader, render_texture: Render_Target_Handle, bound_textures: []Texture_Handle, scissor: Maybe(Rect), blend_mode: Blend_Mode, vertex_buffer: []u8, ) { gl_shd := hm.get(&s.shaders, shd.handle) if gl_shd == nil { return } switch blend_mode { case .Alpha: gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) case .Premultiplied_Alpha: gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) } gl.BindVertexArray(gl_shd.vao) gl.UseProgram(gl_shd.program) assert(len(shd.constants) == len(gl_shd.constants)) cpu_data := shd.constants_data for cidx in 0.. int { return s.width } webgl_get_swapchain_height :: proc() -> int { return s.height } webgl_flip_z :: proc() -> bool { return false } webgl_set_internal_state :: proc(state: rawptr) { s = (^WebGL_State)(state) } create_texture :: proc(width: int, height: int, format: Pixel_Format, data: rawptr) -> Texture_Handle { id := gl.CreateTexture() gl.BindTexture(gl.TEXTURE_2D, id) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, i32(gl.REPEAT)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, i32(gl.REPEAT)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, i32(gl.NEAREST)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, i32(gl.NEAREST)) pf := gl_translate_pixel_format(format) data_size := width*height*pixel_format_size(format) gl.TexImage2D(gl.TEXTURE_2D, 0, pf, i32(width), i32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, data_size, data) tex := WebGL_Texture { id = id, format = format, } return hm.add(&s.textures, tex) } webgl_create_texture :: proc(width: int, height: int, format: Pixel_Format) -> Texture_Handle { return create_texture(width, height, format, nil) } webgl_load_texture :: proc(data: []u8, width: int, height: int, format: Pixel_Format) -> Texture_Handle { return create_texture(width, height, format, raw_data(data)) } webgl_update_texture :: proc(th: Texture_Handle, data: []u8, rect: Rect) -> bool { tex := hm.get(&s.textures, th) if tex == nil { return false } gl.BindTexture(gl.TEXTURE_2D, tex.id) gl.TexSubImage2D(gl.TEXTURE_2D, 0, i32(rect.x), i32(rect.y), i32(rect.w), i32(rect.h), gl.RGBA, gl.UNSIGNED_BYTE, len(data), raw_data(data)) return true } webgl_destroy_texture :: proc(th: Texture_Handle) { tex := hm.get(&s.textures, th) if tex == nil { return } gl.DeleteTexture(tex.id) hm.remove(&s.textures, th) } webgl_create_render_texture :: proc(width: int, height: int) -> (Texture_Handle, Render_Target_Handle) { return {}, {} } webgl_destroy_render_target :: proc(render_target: Render_Target_Handle) { } webgl_set_texture_filter :: proc( th: Texture_Handle, scale_down_filter: Texture_Filter, scale_up_filter: Texture_Filter, mip_filter: Texture_Filter, ) { t := hm.get(&s.textures, th) if t == nil { log.error("Trying to set texture filter for invalid texture %v", th) return } gl.BindTexture(gl.TEXTURE_2D, t.id) min_filter := scale_down_filter == .Point ? gl.NEAREST : gl.LINEAR mag_filter := scale_up_filter == .Point ? gl.NEAREST : gl.LINEAR gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, i32(min_filter)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, i32(mag_filter)) } Shader_Compile_Result_OK :: struct {} Shader_Compile_Result_Error :: string Shader_Compile_Result :: union #no_nil { Shader_Compile_Result_OK, Shader_Compile_Result_Error, } compile_shader_from_source :: proc(shader_data: []byte, shader_type: gl.Enum, err_buf: []u8, err_msg: ^string) -> (shader_id: gl.Shader, ok: bool) { shader_id = gl.CreateShader(shader_type) gl.ShaderSource(shader_id, { string(shader_data) }) gl.CompileShader(shader_id) result := gl.GetShaderiv(shader_id, gl.COMPILE_STATUS) if result != 1 { err_msg^ = gl.GetShaderInfoLog(shader_id, err_buf) gl.DeleteShader(shader_id) return 0, false } return shader_id, true } link_shader :: proc(vs_shader: gl.Shader, fs_shader: gl.Shader, err_buf: []u8, err_msg: ^string) -> (program_id: gl.Program, ok: bool) { program_id = gl.CreateProgram() gl.AttachShader(program_id, vs_shader) gl.AttachShader(program_id, fs_shader) gl.LinkProgram(program_id) status := gl.GetProgramParameter(program_id, gl.LINK_STATUS) if status != 1 { err_msg^ = gl.GetProgramInfoLog(program_id, err_buf) gl.DeleteProgram(program_id) return 0, false } return program_id, true } webgl_load_shader :: proc(vs_source: []byte, fs_source: []byte, desc_allocator := frame_allocator, layout_formats: []Pixel_Format = {}) -> (handle: Shader_Handle, desc: Shader_Desc) { @static err: [1024]u8 err_msg: string vs_shader, vs_shader_ok := compile_shader_from_source(vs_source, gl.VERTEX_SHADER, err[:], &err_msg) if !vs_shader_ok { log.error(err_msg) return {}, {} } fs_shader, fs_shader_ok := compile_shader_from_source(fs_source, gl.FRAGMENT_SHADER, err[:], &err_msg) if !fs_shader_ok { log.error(err_msg) return {}, {} } program, program_ok := link_shader(vs_shader, fs_shader, err[:], &err_msg) if !program_ok { log.error(err_msg) return {}, {} } stride: int { num_attribs := gl.GetProgramParameter(program, gl.ACTIVE_ATTRIBUTES) desc.inputs = make([]Shader_Input, num_attribs, desc_allocator) for i in 0.. 0 ? layout_formats[loc] : get_shader_input_format(name, type) desc.inputs[i] = { name = name, register = int(loc), format = format, type = type, } input_format := get_shader_input_format(name, type) format_size := pixel_format_size(input_format) stride += format_size } } gl_shd := WebGL_Shader { program = program, vao = gl.CreateVertexArray(), } gl.BindVertexArray(gl_shd.vao) offset: int for idx in 0..= num_active_uniform_blocks { continue } size: i32 // TODO investigate if we need std140 layout in the shader or what is fine? gl.GetActiveUniformBlockParameter(program, idx, gl.UNIFORM_BLOCK_DATA_SIZE, &size) if size == 0 { log.errorf("Uniform block %v has size 0", name) continue } buf := gl.CreateBuffer() gl.BindBuffer(gl.UNIFORM_BUFFER, buf) gl.BufferData(gl.UNIFORM_BUFFER, int(size), nil, gl.DYNAMIC_DRAW) gl.BindBufferBase(gl.UNIFORM_BUFFER, idx, buf) gl_shd.constant_buffers[cb_idx] = { block_index = idx, buffer = buf, size = int(size), } num_uniforms: i32 gl.GetActiveUniformBlockParameter(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num_uniforms) uniform_indices := make([]i32, num_uniforms, frame_allocator) gl.GetActiveUniformBlockParameter(program, idx, gl.UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, raw_data(uniform_indices)) for var_idx in 0.. int { sz: int switch t { case gl.FLOAT: sz = 4*1 case gl.FLOAT_VEC2: sz = 4*2*1 case gl.FLOAT_MAT2: sz = 4*2*2 case gl.FLOAT_MAT2x3: sz = 4*2*3 case gl.FLOAT_MAT2x4: sz = 4*2*4 case gl.FLOAT_VEC3: sz = 4*3*1 case gl.FLOAT_MAT3x2: sz = 4*3*2 case gl.FLOAT_MAT3: sz = 4*3*3 case gl.FLOAT_MAT3x4: sz = 4*3*4 case gl.FLOAT_VEC4: sz = 4*4*1 case gl.FLOAT_MAT4x2: sz = 4*4*2 case gl.FLOAT_MAT4x3: sz = 4*4*3 case gl.FLOAT_MAT4: sz = 4*4*4 case gl.BOOL: sz = 4*1 case gl.BOOL_VEC2: sz = 4*2 case gl.BOOL_VEC3: sz = 4*3 case gl.BOOL_VEC4: sz = 4*4 case gl.INT: sz = 4*1 case gl.INT_VEC2: sz = 4*2 case gl.INT_VEC3: sz = 4*3 case gl.INT_VEC4: sz = 4*4 case gl.UNSIGNED_INT: sz = 4*1 case gl.UNSIGNED_INT_VEC2: sz = 4*2 case gl.UNSIGNED_INT_VEC3: sz = 4*3 case gl.UNSIGNED_INT_VEC4: sz = 4*4 case: log.errorf("Unhandled uniform type: %x", t) } return sz } gl_translate_pixel_format :: proc(f: Pixel_Format) -> gl.Enum { switch f { case .RGBA_32_Float: return gl.RGBA case .RGB_32_Float: return gl.RGB case .RG_32_Float: return gl.RG case .R_32_Float: return gl.RED // IS THIS STUFF CORRECT? Compare to GL backend // Do we need float textures? What is happening... case .RGBA_8_Norm: return gl.RGBA case .RG_8_Norm: return gl.RG case .R_8_Norm: return gl.RED case .R_8_UInt: return gl.RED case .Unknown: fallthrough case: log.error("Unhandled pixel format %v", f) } return 0 } gl_describe_pixel_format :: proc(f: Pixel_Format) -> (format: gl.Enum, num_components: int, normalized: bool) { switch f { case .RGBA_32_Float: return gl.FLOAT, 4, false case .RGB_32_Float: return gl.FLOAT, 3, false case .RG_32_Float: return gl.FLOAT, 2, false case .R_32_Float: return gl.FLOAT, 1, false case .RGBA_8_Norm: return gl.UNSIGNED_BYTE, 4, true case .RG_8_Norm: return gl.UNSIGNED_BYTE, 2, true case .R_8_Norm: return gl.UNSIGNED_BYTE, 1, true case .R_8_UInt: return gl.BYTE, 1, false case .Unknown: } log.errorf("Unknown format %x", format) return 0, 0, false } webgl_destroy_shader :: proc(h: Shader_Handle) { shd := hm.get(&s.shaders, h) if shd == nil { log.errorf("Invalid shader: %v", h) return } delete(shd.constant_buffers, s.allocator) delete(shd.constants, s.allocator) delete(shd.texture_bindings, s.allocator) } webgl_default_shader_vertex_source :: proc() -> []byte { vertex_source := #load("render_backend_webgl_default_vertex_shader.glsl") return vertex_source } webgl_default_shader_fragment_source :: proc() -> []byte { fragment_source := #load("render_backend_webgl_default_fragment_shader.glsl") return fragment_source }