diff --git a/.gitignore b/.gitignore index 0af3826..042d5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/* **/.ccls-cache perf.data* +*.rdc +test_renderer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a19a0d7 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* + scripts/compile_shaders.sh + c3c compile-run -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 diff --git a/lib/ugui.c3l/LICENSE b/lib/ugui.c3l/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/lib/ugui.c3l/README.md b/lib/ugui.c3l/README.md new file mode 100644 index 0000000..151118a --- /dev/null +++ b/lib/ugui.c3l/README.md @@ -0,0 +1 @@ +Welcome to the ugui library. diff --git a/lib/ugui.c3l/linux-x64/.gitkeep b/lib/ugui.c3l/linux-x64/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/ugui.c3l/manifest.json b/lib/ugui.c3l/manifest.json new file mode 100644 index 0000000..8211665 --- /dev/null +++ b/lib/ugui.c3l/manifest.json @@ -0,0 +1,11 @@ +{ + "provides" : "ugui", + "sources" : [ "src/**" ], + "targets" : { + "linux-x64" : { + "link-args" : [], + "dependencies" : ["schrift", "grapheme", "mqoi"], + "linked-libraries" : [] + } + } +} \ No newline at end of file diff --git a/lib/ugui.c3l/scripts/.gitkeep b/lib/ugui.c3l/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/cache.c3 b/lib/ugui.c3l/src/cache.c3 similarity index 100% rename from src/cache.c3 rename to lib/ugui.c3l/src/cache.c3 diff --git a/src/fifo.c3 b/lib/ugui.c3l/src/fifo.c3 similarity index 100% rename from src/fifo.c3 rename to lib/ugui.c3l/src/fifo.c3 diff --git a/src/ugui_atlas.c3 b/lib/ugui.c3l/src/ugui_atlas.c3 similarity index 100% rename from src/ugui_atlas.c3 rename to lib/ugui.c3l/src/ugui_atlas.c3 diff --git a/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 similarity index 100% rename from src/ugui_button.c3 rename to lib/ugui.c3l/src/ugui_button.c3 diff --git a/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 similarity index 100% rename from src/ugui_cmd.c3 rename to lib/ugui.c3l/src/ugui_cmd.c3 diff --git a/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 similarity index 100% rename from src/ugui_core.c3 rename to lib/ugui.c3l/src/ugui_core.c3 diff --git a/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 similarity index 100% rename from src/ugui_div.c3 rename to lib/ugui.c3l/src/ugui_div.c3 diff --git a/src/ugui_font.c3 b/lib/ugui.c3l/src/ugui_font.c3 similarity index 100% rename from src/ugui_font.c3 rename to lib/ugui.c3l/src/ugui_font.c3 diff --git a/src/ugui_input.c3 b/lib/ugui.c3l/src/ugui_input.c3 similarity index 100% rename from src/ugui_input.c3 rename to lib/ugui.c3l/src/ugui_input.c3 diff --git a/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 similarity index 100% rename from src/ugui_layout.c3 rename to lib/ugui.c3l/src/ugui_layout.c3 diff --git a/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 similarity index 100% rename from src/ugui_shapes.c3 rename to lib/ugui.c3l/src/ugui_shapes.c3 diff --git a/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 similarity index 100% rename from src/ugui_slider.c3 rename to lib/ugui.c3l/src/ugui_slider.c3 diff --git a/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 similarity index 100% rename from src/ugui_sprite.c3 rename to lib/ugui.c3l/src/ugui_sprite.c3 diff --git a/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 similarity index 100% rename from src/ugui_text.c3 rename to lib/ugui.c3l/src/ugui_text.c3 diff --git a/src/vtree.c3 b/lib/ugui.c3l/src/vtree.c3 similarity index 100% rename from src/vtree.c3 rename to lib/ugui.c3l/src/vtree.c3 diff --git a/project.json b/project.json index 8de760f..21e419c 100644 --- a/project.json +++ b/project.json @@ -2,7 +2,7 @@ "langrev": "1", "warnings": ["no-unused"], "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"], - "dependencies": ["raylib55", "schrift", "grapheme", "mqoi", "sdl3"], + "dependencies": ["raylib55", "sdl3", "ugui"], "features": [], "authors": ["Alessandro Mauri "], "version": "0.1.0", diff --git a/resources/shaders/compiled/rect.frag.spv b/resources/shaders/compiled/rect.frag.spv new file mode 100644 index 0000000..3cc8f05 Binary files /dev/null and b/resources/shaders/compiled/rect.frag.spv differ diff --git a/resources/shaders/compiled/rect.vert.spv b/resources/shaders/compiled/rect.vert.spv new file mode 100644 index 0000000..439d76d Binary files /dev/null and b/resources/shaders/compiled/rect.vert.spv differ diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl new file mode 100644 index 0000000..1e43b1a --- /dev/null +++ b/resources/shaders/source/font.frag.glsl @@ -0,0 +1,18 @@ +#version 330 core + +// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels +uniform ivec2 viewsize; +uniform ivec2 texturesize; + +// texture uv coordinate in texture space +in vec2 uv; +uniform sampler2DRect ts; + +const vec3 textcolor = vec3(1.0, 1.0, 1.0); + + +void main() +{ + //gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f); + gl_FragColor = vec4(textcolor, texture(ts, uv)); +} diff --git a/resources/shaders/source/font.vert.glsl b/resources/shaders/source/font.vert.glsl new file mode 100644 index 0000000..8d429cb --- /dev/null +++ b/resources/shaders/source/font.vert.glsl @@ -0,0 +1,26 @@ +#version 330 core + +// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels +uniform ivec2 viewsize; +uniform ivec2 texturesize; + +// both position and and uv are in pixels, they where converted to floats when +// passed to the shader +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 txcoord; + +out vec2 uv; + +void main() +{ + vec2 v = vec2(float(viewsize.x), float(viewsize.y)); + +// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f); + vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); + vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); + + gl_Position = pos; + // since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to + // be normalized + uv = vec2(txcoord.x, txcoord.y); +} diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl new file mode 100644 index 0000000..3466a16 --- /dev/null +++ b/resources/shaders/source/rect.frag.glsl @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec4 col; +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = col; +} \ No newline at end of file diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl new file mode 100644 index 0000000..abe367a --- /dev/null +++ b/resources/shaders/source/rect.vert.glsl @@ -0,0 +1,25 @@ +#version 450 + +layout(set = 1, binding = 0) uniform Viewport { + ivec2 view; + ivec2 off; +}; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 uv; +layout(location = 2) in ivec4 color; + +layout(location = 0) out vec4 col; + +void main() +{ + //vec2 shift = vec2(0); + vec2 shift; + shift.x = float(off.x) / view.x; + shift.y = -(float(off.y) / view.y); + + vec2 pos = position + shift; + + gl_Position = vec4(pos.x, pos.y, 0.0, 1.0); + col = vec4(color) / 255.0; +} diff --git a/scripts/compile_shaders.sh b/scripts/compile_shaders.sh new file mode 100755 index 0000000..336cdf1 --- /dev/null +++ b/scripts/compile_shaders.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +source_directory="./resources/shaders/source" +compiled_directory="./resources/shaders/compiled" +#vulkan_version="1.0" + +mkdir -p "$compiled_directory" +rm -f "$compiled_directory"/* + +echo "Compiling from $source_directory -> $compiled_directory" + +for file in "$source_directory"/*; do + [ -f "$file" ] || continue # Skip non-files + filename=$(basename "$file") + + # Extract filename parts using POSIX parameter expansion + shader_language="${filename##*.}" + stage_part="${filename%.*}" # Remove extension + base_name="${stage_part%.*}" # Remove stage + stage="${stage_part#"$base_name"}" + stage="${stage#.}" # Remove leading dot + + # Skip if not in base.stage.glsl format + [ "$shader_language" = "glsl" ] && [ -n "$base_name" ] && [ -n "$stage" ] || continue + + # Handle HLSL rejection + if [ "$shader_language" != "glsl" ]; then + echo "Error: Only GLSL shaders are supported" >&2 + exit 1 + fi + + # Compile based on shader stage + case "$stage" in + frag|vert) + echo "$stage $filename > $base_name.$stage.spv" + glslc -O0 -g -fshader-stage="$stage" "$file" -o "$compiled_directory/$base_name.$stage.spv" + ;; + esac +done + +tree "$compiled_directory" \ No newline at end of file diff --git a/src/renderer.c3 b/src/renderer.c3 new file mode 100644 index 0000000..99f10a5 --- /dev/null +++ b/src/renderer.c3 @@ -0,0 +1,628 @@ +module sdlrenderer::ren; + +// 2D renderer for ugui, based on SDL3 using the new GPU API +// TODO: use unreachable() instead of the combo eprintfn();exit(1); since it does the same thing +// but also adds a helpful stack trace + +import std::io; +import std::core::mem; +import sdl3::sdl; +import libc; +import std::collections::list; + +struct Shader { + sdl::GPUShader* frag; + sdl::GPUShader* vert; + uint id; +} + +struct Pipeline { + sdl::GPUGraphicsPipeline* pipeline; + uint id; +} + +struct Texture { + sdl::GPUTexture* texture; + sdl::GPUSampler* sampler; + ushort width, height; + uint id; +} + +// gpu buffer that contains a single quad +struct QuadBuffer { + sdl::GPUBuffer* vert_buf; + sdl::GPUBuffer* idx_buf; + bool initialized; +} + +alias ShaderList = List{Shader}; +alias PipelineList = List{Pipeline}; +alias TextureList = List{Texture}; + +struct Renderer { + sdl::Window* win; + sdl::GPUDevice* gpu; + QuadBuffer quad_buffer; + ShaderList shaders; + PipelineList pipelines; + TextureList textures; +} + +// how each vertex is represented in the gpu +struct Vertex @packed { + struct pos { + float x, y; + } + struct uv { + float u, v; + } + struct col { // FIXME: this is shit + union { + char r, g, b, a; + char[4] arr; + uint u; + } + } +} + +struct Quad @packed { + struct vertices { + Vertex v1,v2,v3,v4; + } + struct indices { + short i1,i2,i3,i4,i5,i6; + } +} + +const int DEBUG = 1; + +fn void Renderer.init(&self, ZString title) +{ + // set wayland hint automagically +$if DEBUG == 0: + bool has_wayland = false; + for (int i = 0; i < sdl::get_num_video_drivers(); i++) { + ZString driver = sdl::get_video_driver(i); + if (driver.str_view() == "wayland") { + has_wayland = true; + break; + } + } + + if (has_wayland) { + sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland"); + } +$else + sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); +$endif + + + sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1"); + + // init subsystems + if (!sdl::init(INIT_VIDEO)) { + io::eprintfn("sdl error: %s", sdl::get_error()); + libc::exit(1); + } + + // create the window + self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN); + if (self.win == null) { + io::eprintfn("sdl error: %s", sdl::get_error()); + libc::exit(1); + } + + // get the gpu device handle + self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan"); + if (self.gpu == null) { + io::eprintfn("failed to create gpu device: %s", sdl::get_error()); + libc::exit(1); + } + + if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) { + io::eprintfn("failed to claim window for use with gpu: %s", sdl::get_error()); + libc::exit(1); + } + + // initialize the quad buffer + self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof} + ); + if (self.quad_buffer.vert_buf == null) { + io::eprintfn("failed to initialize quad buffer (vertex): %s", sdl::get_error()); + libc::exit(1); + } + + self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof} + ); + if (self.quad_buffer.idx_buf == null) { + io::eprintfn("failed to initialize quad buffer (index): %s", sdl::get_error()); + libc::exit(1); + } + + self.quad_buffer.initialized = true; +} + +fn void Renderer.free(&self) +{ + foreach (&s: self.shaders) { + sdl::release_gpu_shader(self.gpu, s.frag); + sdl::release_gpu_shader(self.gpu, s.vert); + } + self.shaders.free(); + + foreach (&p: self.pipelines) { + sdl::release_gpu_graphics_pipeline(self.gpu, p.pipeline); + } + self.pipelines.free(); + + sdl::release_window_from_gpu_device(self.gpu, self.win); + sdl::destroy_gpu_device(self.gpu); + sdl::destroy_window(self.win); + sdl::quit(); +} + +fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code, char[] frag_code, uint textures, uint uniforms) +{ + Shader s; + s.id = name.hash(); + + if (vert_code.len == 0 || frag_code.len == 0) { + unreachable("vertex shader and fragment shader cannot be empty"); + } + + if (vert_code.len > 0) { + // FIXME: these should be passed by parameter and/or automatically determined by parsing + // the shader code + GPUShaderCreateInfo shader_info = { + .code = vert_code.ptr, + .code_size = vert_code.len, + .entrypoint = "main", + .format = GPU_SHADERFORMAT_SPIRV, + .stage = GPU_SHADERSTAGE_VERTEX, + .num_samplers = 0, + .num_uniform_buffers = uniforms, + .num_storage_buffers = 0, + .num_storage_textures = 0 + }; + + s.vert = sdl::create_gpu_shader(self.gpu, &shader_info); + if (s.vert == null) { + io::eprintfn("failed to create gpu vertex shader: %s", sdl::get_error()); + libc::exit(1); + } + } + + if (frag_code.len > 0) { + // FIXME: these should be passed by parameter and/or automatically determined by parsing + // the shader code + GPUShaderCreateInfo shader_info = { + .code = frag_code.ptr, + .code_size = frag_code.len, + .entrypoint = "main", + .format = GPU_SHADERFORMAT_SPIRV, + .stage = GPU_SHADERSTAGE_FRAGMENT, + .num_samplers = textures, + .num_uniform_buffers = 0, + .num_storage_buffers = 0, + .num_storage_textures = 0 + }; + + s.frag = sdl::create_gpu_shader(self.gpu, &shader_info); + if (s.frag == null) { + io::eprintfn("failed to create gpu fragment shader: %s", sdl::get_error()); + libc::exit(1); + } + } + + // push the shader into the list + self.shaders.push(s); +} + +fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_path, String frag_path, uint textures, uint uniforms) +{ + if (vert_path == "" || frag_path == "") { + unreachable("need both a vertex shader and fragment shader path"); + } + + char[] vert_code; + char[] frag_code; + + // create vertex shader + if (vert_path != "") { + vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); + file::load_buffer(vert_path, vert_code)!!; + } + + // create fragment shader + if (frag_path != "") { + frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); + file::load_buffer(frag_path, frag_code)!!; + } + + self.load_spirv_shader_from_mem(name, vert_code, frag_code, textures, uniforms); + + if (vert_code.ptr) mem::free(vert_code); + if (frag_code.ptr) mem::free(frag_code); +} + +fn Shader* ShaderList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&s: self) { + if (s.id == id) { + return s; + } + } + return null; +} + +fn Pipeline* PipelineList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&p: self) { + if (p.id == id) { + return p; + } + } + return null; +} + +fn Texture* TextureList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&t: self) { + if (t.id == id) { + return t; + } + } + return null; +} + + +// this describes what we want to draw, since for drawing different things we have to change +// the GPUPrimitiveType and GPURasterizerState for the pipeline. +enum PipelineType : (GPUPrimitiveType primitive_type, GPURasterizerState raster_state) { + RECT = {GPU_PRIMITIVETYPE_TRIANGLELIST, {.fill_mode = GPU_FILLMODE_FILL, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, + SPRITE = {GPU_PRIMITIVETYPE_TRIANGLELIST, {.fill_mode = GPU_FILLMODE_FILL, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, + LINE = {GPU_PRIMITIVETYPE_LINELIST, {.fill_mode = GPU_FILLMODE_LINE, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, +} + +// create a graphics pipeline to draw to the window using a set of vertex/fragment shaders +// the pipeline is pushed into the renderer's pipeline list and it will have the same id as +// the shader set. +fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) +{ + Shader *s = self.shaders.get_from_name(shader_name); + if (s == null) { + io::eprintfn("error in creating pipeline: no shader named %s", shader_name); + libc::exit(1); + } + + GPUGraphicsPipelineCreateInfo ci = { + .vertex_shader = s.vert, + .fragment_shader = s.frag, + // This structure specifies how the vertex buffer looks in memory, what it contains + // and what is passed where to the gpu. Each vertex has three attributes, position, + // color and uv coordinates. Since this is a 2D pixel-based renderer the position + // is represented by two floats, the color as 32 bit rgba and the uv also as intgers. + .vertex_input_state = { + // the description of each vertex buffer, for now I use only one buffer + .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){{ + .slot = 0, + .pitch = Vertex.sizeof, + .input_rate = GPU_VERTEXINPUTRATE_VERTEX, + .instance_step_rate = 0, + }}, + .num_vertex_buffers = 1, + // the description of each vertex, each vertex has three properties + .vertex_attributes = (GPUVertexAttribute[]){ + { // at location zero there is the position of the vertex + .location = 0, + .buffer_slot = 0, // only one buffer so always slot zero + .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .offset = Vertex.pos.offsetof, + }, + { // at location one there are the uv coordinates + .location = 1, + .buffer_slot = 0, + .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .offset = Vertex.uv.offsetof, + }, + { // at location two there is the color + .location = 2, + .buffer_slot = 0, + .format = GPU_VERTEXELEMENTFORMAT_UBYTE4, // 4x8bit unsigned rgba format + .offset = Vertex.col.offsetof, + } + }, + .num_vertex_attributes = 3, + }, + // the pipeline's primitive type and rasterizer state differs based on what needs to + // be drawn + .primitive_type = type.primitive_type, + .rasterizer_state = type.raster_state, + .multisample_state = {}, // no multisampling, all zeroes + .depth_stencil_state = {}, // no stencil test, all zeroes + .target_info = { // the target (texture) description + .color_target_descriptions = (GPUColorTargetDescription[]){{ + // rendering happens to the window, so get it's format + .format = sdl::get_gpu_swapchain_texture_format(self.gpu, self.win), + .blend_state = { + // alpha blending on everything + // https://en.wikipedia.org/wiki/Alpha_compositing + .src_color_blendfactor = GPU_BLENDFACTOR_SRC_ALPHA, + .dst_color_blendfactor = GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .color_blend_op = GPU_BLENDOP_ADD, + .src_alpha_blendfactor = GPU_BLENDFACTOR_SRC_ALPHA, + .dst_alpha_blendfactor = GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .alpha_blend_op = GPU_BLENDOP_ADD, + .enable_blend = true, + // color write mask is not enabled so all rgba channels are written to + }, + }}, + .num_color_targets = 1, + .depth_stencil_format = {}, // FIXME: no stencil, no depth buffering + .has_depth_stencil_target = false, + }, + }; + + // create the pipeline and add it to the pipeline list + Pipeline p = { + .id = s.id, + .pipeline = sdl::create_gpu_graphics_pipeline(self.gpu, &ci), + }; + + if (p.pipeline == null) { + io::eprintfn("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + self.pipelines.push(p); +} + +enum TextureType : (GPUTextureFormat format) { + FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT, + JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT +} + +// create a new gpu texture from a pixel buffer, the format has to be specified +// the new texture s given an id and pushed into a texture list +fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height) +{ + uint id = name.hash(); + + // the texture description + GPUTextureCreateInfo tci = { + .type = GPU_TEXTURETYPE_2D, + .format = type.format, + // all textures are used with samplers, which means read-only textures that contain data to be sampled + .usage = GPU_TEXTUREUSAGE_SAMPLER, + .width = width, + .height = height, + .layer_count_or_depth = 1, + .num_levels = 0, // no mip maps + // .sample_count not used since the texture is not a render target + }; + + GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci); + if (texture == null) { + io::eprintfn("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + // the sampler description, how the texture should be sampled + GPUSamplerCreateInfo sci = { + .min_filter = GPU_FILTER_LINEAR, // linear interpolation for textures + .mag_filter = GPU_FILTER_LINEAR, + .mipmap_mode = GPU_SAMPLERMIPMAPMODE_NEAREST, + .address_mode_u = GPU_SAMPLERADDRESSMODE_REPEAT, // tiling textures + .address_mode_v = GPU_SAMPLERADDRESSMODE_REPEAT, + .address_mode_w = GPU_SAMPLERADDRESSMODE_REPEAT, + // everything else is not used and not needed + }; + + GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci); + if (sampler == null) { + io::eprintfn("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + Texture t = { + .id = id, + .texture = texture, + .sampler = sampler, + }; + self.textures.push(t); + + // upload the texture data + self.update_texture(name, pixels, width, height); +} + +fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0) +{ + Texture* t = self.textures.get_from_name(name); + if (t == null || t.texture == null) { + io::eprintf("failed updating texture: no texture named %s", name); + libc::exit(1); + } + GPUTexture* texture = t.texture; + + // FIXME: do a better job at validating the copy + if (x > t.width || y > t.height) { + io::eprintf("failed updating texture: attempting to copy outside of the texture region", name); + libc::exit(1); + } + + // upload image data + GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmdbuf == null) { + io::eprintfn("failed to upload texture data at acquiring command buffer: %s", sdl::get_error()); + libc::exit(1); + } + GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf); + if (copypass == null) { + io::eprintfn("failed to upload texture data at beginning copy pass: %s", sdl::get_error()); + libc::exit(1); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len} + ); + if (buf == null) { + io::eprintfn("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (gpu_mem == null) { + io::eprintfn("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + // copy the data to the driver's memory + gpu_mem[:pixels.len] = pixels[..]; + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + // upload the data to gpu memory + sdl::upload_to_gpu_texture(copypass, + &&(GPUTextureTransferInfo){.transfer_buffer = buf, .offset = 0}, + &&(GPUTextureRegion){.texture = texture, .x = x, .y = y, .w = width, .h = height, .d = 1}, + false + ); + + sdl::end_gpu_copy_pass(copypass); + if (!sdl::submit_gpu_command_buffer(cmdbuf)) { + io::eprintfn("failed to upload texture data at command buffer submission: %s", sdl::get_error()); + libc::exit(1); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); +} + + +macro void Vertex.norm(&p, float w, float h) +{ + p.pos.x = p.pos.x * 2.0 / w - 1.0; + p.pos.y = -(p.pos.y * 2.0 / h - 1.0); +} + +// an highly inefficient way to draw a single quad, no batching, per-quad upload +fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name) +{ + // upload the quad data to the gpu + if (self.quad_buffer.initialized == false) { + io::eprintfn("quad buffer not initialized"); + libc::exit(1); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (buf == null) { + io::eprintfn("failed to create gpu transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (quad == null) { + io::eprintfn("failed to map gpu transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + /* v1 v4 + * +-------------+ + * | _/| + * | _/ | + * | 1 _/ | + * | _/ | + * | _/ | + * | _/ 2 | + * |/ | + * +-------------+ + * v2 v3 + */ + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .col.u = color}; + quad.vertices.v2 = {.pos = {.x = x, .y = (float)y+h}, .col.u = color}; + quad.vertices.v3 = {.pos = {.x = (float)x+w, .y = (float)y+h}, .col.u = color}; + quad.vertices.v4 = {.pos = {.x = (float)x+w, .y = y}, .col.u = color}; + + quad.vertices.v1.norm(640.0, 480.0); + quad.vertices.v2.norm(640.0, 480.0); + quad.vertices.v3.norm(640.0, 480.0); + quad.vertices.v4.norm(640.0, 480.0); + + // triangle 1 + quad.indices.i1 = 0; // v1 + quad.indices.i2 = 1; // v2 + quad.indices.i3 = 3; // v4 + // triangle 2 + quad.indices.i4 = 1; // v2 + quad.indices.i5 = 2; // v3 + quad.indices.i6 = 3; // v4 + + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + io::eprintfn("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + libc::exit(1); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, + &&(GPUBufferRegion){.buffer = self.quad_buffer.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, + &&(GPUBufferRegion){.buffer = self.quad_buffer.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); + sdl::wait_for_gpu_idle(self.gpu); + +/* + // now finally draw the quad + // if we are not in a render pass then we can't render shit + if (self.render_cmd == null) { + unreachable("start rendering first before trying to render a quad"); + } + + // FIXME: this could be done at the start of rendering + GPUTexture* t; + if (!sdl::wait_and_acquire_gpu_swapchain_texture(self.render_cmd, self.win, &t, null, null)) { + unreachable("failed to acquire swapchain texture: %s", sdl::get_error()); + } + + // TODO: begin render pass + + Pipeline* p = self.pipelines.get_from_name(shader_name); + if (p == null) { + unreachable("no pipeline named: %s", shader_name); + } + + // bind the data + sdl::bind_gpu_graphics_pipeline(self.render_pass, pipeline); + sdl::bind_gpu_vertex_buffer(self.render_pass, 0, + &&(GPUBufferBinding){.buffer = self.quad_buffer.vert_buf, .offset = 0}, 1 + ); + sdl::bind_gpu_index_buffer(self.render_pass, 0, + &&(GPUBufferBinding){.buffer = self.quad_buffer.idx_buf, .offset = 0}, 1 + ); + + sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, 0, 0, 0); +*/ +} + +// TODO: fn Renderer.draw_quad, it has to use a vertex buffer and an index buffer +// TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture +// TODO: fn Renderer.begin_render +// TODO: fn Renderer.end_render \ No newline at end of file diff --git a/test_renderer.c3 b/test_renderer.c3 new file mode 100644 index 0000000..63f0d5d --- /dev/null +++ b/test_renderer.c3 @@ -0,0 +1,73 @@ +import sdlrenderer::ren; +import std::io; +import std::thread; +import sdl3::sdl; + +struct Viewsize @align(16) { + int w, h; + int ox, oy; +} + +fn int main() +{ + ren::Renderer ren; + ren.init("test window"); + + ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); + ren.create_pipeline("rect shader", RECT); + + for (int i = 0; i < 10; i++) { + + ren.draw_rect(100,100,100,100,0xff00ff00,""); + + GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); + GPUTexture* swapchain_texture; + sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); + + // FIXME: if doing damage tracking DO NOT clear the screen + GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf, + &&(GPUColorTargetInfo){ + .texture = swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, + .load_op = GPU_LOADOP_CLEAR, // clear the screen at the start of the render pass + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + + if (pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; + if (p == null) { + unreachable("no pipeline"); + } + + sdl::bind_gpu_graphics_pipeline(pass, p); + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + Viewsize v = {.w = 640, .h = 480}; + v.ox = 50*i; + v.oy = 50*i; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); + + sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); + + sdl::end_gpu_render_pass(pass); + sdl::submit_gpu_command_buffer(cmdbuf); + + thread::sleep_ms(250); + } + + ren.free(); + return 0; +} \ No newline at end of file