Created
April 16, 2025 09:56
-
-
Save p1xelHer0/3ecc3619d3f0d2f51f03d7d71329aa3c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package game | |
| import "core:c" | |
| import "core:fmt" | |
| import "core:math" | |
| import "core:math/rand" | |
| import "core:slice" | |
| import "core:image/png" | |
| import SDL "vendor:sdl3" | |
| Game_Mem :: struct | |
| { | |
| GFX_Renderer: GFX_Renderer, | |
| WINDOW: ^SDL.Window, | |
| RENDERER: ^SDL.Renderer, | |
| DEVICE: ^SDL.GPUDevice, | |
| } | |
| GFX_Renderer :: struct | |
| { | |
| pipeline: ^SDL.GPUGraphicsPipeline, | |
| sampler: ^SDL.GPUSampler, | |
| texture: ^SDL.GPUTexture, | |
| sprite_atlas: ^SDL.GPUTexture, | |
| sprite_data_transfer_buf: ^SDL.GPUTransferBuffer, | |
| sprite_data_buf: ^SDL.GPUBuffer, | |
| } | |
| G: ^Game_Mem | |
| /**/ when ODIN_OS == .Windows | |
| { | |
| SHADER_VERT :: #load("shaders/compiled/shader.vert.dxil") | |
| SHADER_FRAG :: #load("shaders/compiled/shader.frag.dxil") | |
| SHADER_FORMAT :: SDL.GPUShaderFormatFlag.DXIL | |
| SHADER_ENTRYPOINT :: cstring("main") | |
| } | |
| else when ODIN_OS == .Darwin | |
| { | |
| SHADER_VERT :: #load("shaders/compiled/shader.vert.msl") | |
| SHADER_FRAG :: #load("shaders/compiled/shader.frag.msl") | |
| SHADER_FORMAT :: SDL.GPUShaderFormatFlag.MSL | |
| SHADER_ENTRYPOINT :: cstring("main0") | |
| } | |
| else | |
| { | |
| SHADER_VERT :: #load("shaders/compiled/shader.vert.spv") | |
| SHADER_FRAG :: #load("shaders/compiled/shader.frag.spv") | |
| SHADER_FORMAT :: SDL.GPUShaderFormatFlag.SPIRV | |
| SHADER_ENTRYPOINT :: cstring("main") | |
| } | |
| Sprite_Instance :: struct | |
| { | |
| position: [3]f32, | |
| rotation: f32, | |
| w, h, padding_a, padding_b: f32, | |
| tex_u, tex_v, tex_w, tex_h: f32, | |
| color: [4]f32, | |
| } | |
| SPRITE_ATLAS :: #load("assets/phosphor.png") | |
| BUDGET_SPRITES :: 8192 | |
| U_COORDS :: [4]f32 { 0.0, 0.5, 0.0, 0.5 }; | |
| V_COORDS :: [4]f32 { 0.5, 0.0, 0.5, 0.5 }; | |
| GAME_WIDTH :: f32(640) | |
| GAME_HEIGHT :: f32(480) | |
| gfx_create_orthographic_offcenter :: proc(l, r, b, t, z_n, z_f: f32) -> matrix[4, 4]f32 | |
| { | |
| return matrix[4, 4]f32 { | |
| 2.0 / (r - l), 0.0, 0.0, (l + r) / (l - r), | |
| 0.0, 2.0 / (t - b), 0.0, (t + b) / (b - t), | |
| 0.0, 0.0, 1.0 / (z_n - z_f), z_n / (z_n - z_f), | |
| 0.0, 0.0, 0.0, 1.0, | |
| } | |
| } | |
| gfx_get_shader_createinfo :: proc( | |
| shader_code: []u8, | |
| shader_stage: SDL.GPUShaderStage, | |
| num_samplers, num_storage_textures, num_storage_buffers, num_uniform_buffers: int | |
| ) -> SDL.GPUShaderCreateInfo | |
| { | |
| return SDL.GPUShaderCreateInfo { | |
| code_size = len(shader_code), | |
| code = raw_data(shader_code), | |
| entrypoint = SHADER_ENTRYPOINT, | |
| format = {SHADER_FORMAT}, | |
| stage = shader_stage, | |
| num_samplers = u32(num_samplers), | |
| num_storage_textures = u32(num_storage_textures), | |
| num_storage_buffers = u32(num_storage_buffers), | |
| num_uniform_buffers = u32(num_uniform_buffers), | |
| } | |
| } | |
| gfx_get_offscreen_pipeline_createinfo :: proc(vertex_shader, fragment_shader: ^SDL.GPUShader, allocator := context.allocator, loc := #caller_location) -> SDL.GPUGraphicsPipelineCreateInfo | |
| { | |
| color_target_descriptions := make([]SDL.GPUColorTargetDescription, 1, allocator) | |
| color_target_descriptions[0] = { | |
| format = SDL.GetGPUSwapchainTextureFormat(G.DEVICE, G.WINDOW), | |
| blend_state = SDL.GPUColorTargetBlendState { | |
| enable_blend = true, | |
| color_blend_op = .ADD, | |
| alpha_blend_op = .ADD, | |
| src_color_blendfactor = .SRC_ALPHA, | |
| dst_color_blendfactor = .ONE_MINUS_SRC_ALPHA, | |
| src_alpha_blendfactor = .SRC_ALPHA, | |
| dst_alpha_blendfactor = .ONE_MINUS_SRC_ALPHA, | |
| }, | |
| } | |
| return SDL.GPUGraphicsPipelineCreateInfo { | |
| target_info = SDL.GPUGraphicsPipelineTargetInfo { | |
| num_color_targets = 1, | |
| color_target_descriptions = raw_data(color_target_descriptions), | |
| }, | |
| primitive_type = .TRIANGLELIST, | |
| vertex_shader = vertex_shader, | |
| fragment_shader = fragment_shader, | |
| } | |
| } | |
| game_init :: proc() -> bool { | |
| G = new(Game_Mem) | |
| defer free_all(context.temp_allocator) | |
| if !SDL.SetAppMetadata("hot!", "1.0", "game_thing") | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("SetAppMetadata: %v", err) | |
| return false | |
| } | |
| if !SDL.Init({.VIDEO}) | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("Init: %v", err) | |
| return false | |
| } | |
| G.WINDOW = SDL.CreateWindow(title = "Example", w = i32(GAME_WIDTH), h = i32(GAME_HEIGHT), flags = {.RESIZABLE}) | |
| if G.WINDOW == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("CreateWindow: %v", err) | |
| return false | |
| } | |
| G.DEVICE = SDL.CreateGPUDevice(format_flags = {SHADER_FORMAT}, debug_mode = true, name = nil) | |
| if G.DEVICE == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("CreateGPUDevice: %v", err) | |
| return false | |
| } | |
| if !SDL.ClaimWindowForGPUDevice(G.DEVICE, G.WINDOW) | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("ClaimWindowForGPUDevice: %v", err) | |
| return false | |
| } | |
| present_mode := SDL.GPUPresentMode.VSYNC | |
| /**/ if SDL.WindowSupportsGPUPresentMode(G.DEVICE, G.WINDOW, present_mode = .IMMEDIATE) | |
| { | |
| present_mode = .IMMEDIATE | |
| } | |
| else if SDL.WindowSupportsGPUPresentMode(G.DEVICE, G.WINDOW, present_mode = .MAILBOX) | |
| { | |
| present_mode = .MAILBOX | |
| } | |
| if !SDL.SetGPUSwapchainParameters(G.DEVICE, G.WINDOW, swapchain_composition = .SDR, present_mode = present_mode) | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("SetGPUSwapchainParameters: %v", err) | |
| return false | |
| } | |
| // Create the shaders | |
| vert_shader_desc := gfx_get_shader_createinfo(SHADER_VERT, .VERTEX, | |
| num_samplers = 0, | |
| num_storage_buffers = 1, | |
| num_storage_textures = 0, | |
| num_uniform_buffers = 1, | |
| ) | |
| vert_shader := SDL.CreateGPUShader(G.DEVICE, vert_shader_desc) | |
| if vert_shader == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("CreateGPUShader .VERTEX: %v", err) | |
| return false | |
| } | |
| frag_shader_desc := gfx_get_shader_createinfo(SHADER_FRAG, .FRAGMENT, | |
| num_samplers = 1, | |
| num_storage_textures = 0, | |
| num_storage_buffers = 0, | |
| num_uniform_buffers = 0, | |
| ) | |
| frag_shader := SDL.CreateGPUShader(G.DEVICE, frag_shader_desc) | |
| if frag_shader == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("CreateGPUShader .FRAGMENT: %v", err) | |
| return false | |
| } | |
| // Create the sprite render pipeline | |
| pipeline_createinfo := gfx_get_offscreen_pipeline_createinfo(vert_shader, frag_shader, context.temp_allocator) | |
| G.GFX_Renderer.pipeline = SDL.CreateGPUGraphicsPipeline(G.DEVICE, pipeline_createinfo) | |
| if G.GFX_Renderer.pipeline == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("CreateGPUGraphicsPipeline: %v", err) | |
| return false | |
| } | |
| SDL.ReleaseGPUShader(G.DEVICE, vert_shader) | |
| SDL.ReleaseGPUShader(G.DEVICE, frag_shader) | |
| image_surface := SDL.LoadBMP("../../src/assets/ravioli_atlas.bmp") | |
| if image_surface == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("LoadBMP: %v", err) | |
| return false | |
| } | |
| texture_transfer_buffer := SDL.CreateGPUTransferBuffer(G.DEVICE, SDL.GPUTransferBufferCreateInfo { | |
| usage = .UPLOAD, | |
| size = u32(image_surface.w * image_surface.h * 4), | |
| }) | |
| texture_transfer_ptr := SDL.MapGPUTransferBuffer(G.DEVICE, texture_transfer_buffer, false) | |
| SDL.memcpy(texture_transfer_ptr, image_surface.pixels, uint(image_surface.w * image_surface.h * 4)) | |
| SDL.UnmapGPUTransferBuffer(G.DEVICE, texture_transfer_buffer) | |
| G.GFX_Renderer.texture = SDL.CreateGPUTexture(G.DEVICE, SDL.GPUTextureCreateInfo { | |
| type = .D2, | |
| format = .R8G8B8A8_UNORM, | |
| width = u32(image_surface.w), | |
| height = u32(image_surface.h), | |
| layer_count_or_depth = 1, | |
| num_levels = 1, | |
| usage = {.SAMPLER}, | |
| }) | |
| SDL.SetGPUTextureName(G.DEVICE, G.GFX_Renderer.texture, "Ravioli!") | |
| sprite_atlas, sprite_atlas_err := png.load_from_bytes(SPRITE_ATLAS, allocator = context.temp_allocator) | |
| if sprite_atlas_err != nil | |
| { | |
| fmt.eprintfln("failed to load_from_bytes from assets.ATLAS: %v", sprite_atlas_err) | |
| return false | |
| } | |
| sprite_atlas_width := u32(sprite_atlas.width) | |
| sprite_atlas_height := u32(sprite_atlas.height) | |
| sprite_atlas_size := slice.size(sprite_atlas.pixels.buf[:]) | |
| sprite_atlas_transfer_buf := SDL.CreateGPUTransferBuffer(G.DEVICE, SDL.GPUTransferBufferCreateInfo { | |
| usage = .UPLOAD, | |
| size = u32(sprite_atlas_size), | |
| }) | |
| sprite_atlas_transfer_ptr := SDL.MapGPUTransferBuffer(G.DEVICE, sprite_atlas_transfer_buf, false) | |
| SDL.memcpy(sprite_atlas_transfer_ptr, raw_data(sprite_atlas.pixels.buf), uint(sprite_atlas_size)) | |
| SDL.UnmapGPUTransferBuffer(G.DEVICE, sprite_atlas_transfer_buf) | |
| G.GFX_Renderer.sprite_atlas = SDL.CreateGPUTexture(G.DEVICE, SDL.GPUTextureCreateInfo { | |
| type = .D2, | |
| format = .R8G8B8A8_UNORM, | |
| width = sprite_atlas_width, | |
| height = sprite_atlas_height, | |
| layer_count_or_depth = 1, | |
| num_levels = 1, | |
| usage = {.SAMPLER}, | |
| }) | |
| SDL.SetGPUTextureName(G.DEVICE, G.GFX_Renderer.sprite_atlas, "SPRITE_ATLAS_PHOSPHOR") | |
| G.GFX_Renderer.sampler = SDL.CreateGPUSampler(G.DEVICE, SDL.GPUSamplerCreateInfo { | |
| min_filter = .NEAREST, | |
| mag_filter = .NEAREST, | |
| mipmap_mode = .NEAREST, | |
| address_mode_u = .CLAMP_TO_EDGE, | |
| address_mode_v = .CLAMP_TO_EDGE, | |
| address_mode_w = .CLAMP_TO_EDGE, | |
| }) | |
| G.GFX_Renderer.sprite_data_transfer_buf = SDL.CreateGPUTransferBuffer(G.DEVICE, SDL.GPUTransferBufferCreateInfo { | |
| usage = .UPLOAD, | |
| size = BUDGET_SPRITES * size_of(Sprite_Instance), | |
| }) | |
| G.GFX_Renderer.sprite_data_buf = SDL.CreateGPUBuffer(G.DEVICE, SDL.GPUBufferCreateInfo { | |
| usage = {.GRAPHICS_STORAGE_READ}, | |
| size = BUDGET_SPRITES * size_of(Sprite_Instance), | |
| }) | |
| SDL.SetGPUBufferName(G.DEVICE, G.GFX_Renderer.sprite_data_buf, "Sprite Buffer") | |
| // Transfer the up-front data (the sprite atlas) | |
| upload_cmd_buf := SDL.AcquireGPUCommandBuffer(G.DEVICE) | |
| copy_pass := SDL.BeginGPUCopyPass(upload_cmd_buf) | |
| SDL.UploadToGPUTexture( | |
| copy_pass, | |
| SDL.GPUTextureTransferInfo { | |
| transfer_buffer = texture_transfer_buffer, | |
| offset = 0, // Zeroes out the rest | |
| }, | |
| SDL.GPUTextureRegion { | |
| texture = G.GFX_Renderer.texture, | |
| w = u32(image_surface.w), | |
| h = u32(image_surface.h), | |
| d = 1, | |
| }, | |
| false | |
| ) | |
| SDL.UploadToGPUTexture( | |
| copy_pass, | |
| SDL.GPUTextureTransferInfo { | |
| transfer_buffer = sprite_atlas_transfer_buf, | |
| offset = 0, // Zeroes out the rest | |
| }, | |
| SDL.GPUTextureRegion { | |
| texture = G.GFX_Renderer.sprite_atlas, | |
| w = sprite_atlas_width, | |
| h = sprite_atlas_height, | |
| d = 1, | |
| }, | |
| false | |
| ) | |
| SDL.EndGPUCopyPass(copy_pass) | |
| _ = SDL.SubmitGPUCommandBuffer(upload_cmd_buf) | |
| SDL.DestroySurface(image_surface) | |
| SDL.ReleaseGPUTransferBuffer(G.DEVICE, texture_transfer_buffer) | |
| SDL.ReleaseGPUTransferBuffer(G.DEVICE, sprite_atlas_transfer_buf) | |
| return true | |
| } | |
| game_frame :: proc() | |
| { | |
| camera_matrix := gfx_create_orthographic_offcenter(0, GAME_WIDTH, GAME_HEIGHT, 0, 0, -1) | |
| cmd_buf := SDL.AcquireGPUCommandBuffer(G.DEVICE) | |
| if cmd_buf == nil | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("AcquireGPUCommandBuffer: %v", err) | |
| return | |
| } | |
| swapchain_texture: ^SDL.GPUTexture | |
| if !SDL.WaitAndAcquireGPUSwapchainTexture(cmd_buf, G.WINDOW, &swapchain_texture, nil, nil) | |
| { | |
| err := SDL.GetError() | |
| fmt.eprintfln("WaitAndAcquireGPUSwapchainTexture: %v", err) | |
| return | |
| } | |
| if swapchain_texture != nil | |
| { | |
| // Build sprite instance transfer | |
| sprite_instances := ([^]Sprite_Instance)(SDL.MapGPUTransferBuffer(G.DEVICE, G.GFX_Renderer.sprite_data_transfer_buf, true)) | |
| u_coords := U_COORDS | |
| v_coords := V_COORDS | |
| for &sprite_instance in sprite_instances[:BUDGET_SPRITES] | |
| { | |
| ravioli := rand.int31_max(4) | |
| sprite_instance.position.x = rand.float32_range(0, GAME_WIDTH) | |
| sprite_instance.position.y = rand.float32_range(0, GAME_HEIGHT) | |
| sprite_instance.position.z = 0 | |
| sprite_instance.rotation = rand.float32() * math.PI | |
| sprite_instance.w = 32 | |
| sprite_instance.h = 32 | |
| sprite_instance.tex_u = u_coords[ravioli] | |
| sprite_instance.tex_v = v_coords[ravioli] | |
| sprite_instance.tex_w = 0.5 | |
| sprite_instance.tex_h = 0.5 | |
| sprite_instance.color = {1, 1, 1, 1} | |
| } | |
| SDL.UnmapGPUTransferBuffer(G.DEVICE, G.GFX_Renderer.sprite_data_transfer_buf) | |
| // Upload instance data | |
| copy_pass := SDL.BeginGPUCopyPass(cmd_buf) | |
| SDL.UploadToGPUBuffer( | |
| copy_pass, | |
| source = SDL.GPUTransferBufferLocation { | |
| transfer_buffer = G.GFX_Renderer.sprite_data_transfer_buf, | |
| offset = 0, | |
| }, | |
| destination = SDL.GPUBufferRegion { | |
| buffer = G.GFX_Renderer.sprite_data_buf, | |
| offset = 0, | |
| size = BUDGET_SPRITES * size_of(Sprite_Instance), | |
| }, | |
| cycle = true | |
| ) | |
| SDL.EndGPUCopyPass(copy_pass) | |
| // Render sprites | |
| render_pass := SDL.BeginGPURenderPass( | |
| cmd_buf, | |
| &(SDL.GPUColorTargetInfo { | |
| texture = swapchain_texture, | |
| cycle = false, | |
| load_op = .CLEAR, | |
| store_op = .STORE, | |
| clear_color = {0.1, 0.2, 0.3, 1.0}, | |
| }), | |
| 1, | |
| nil | |
| ) | |
| SDL.BindGPUGraphicsPipeline(render_pass, G.GFX_Renderer.pipeline) | |
| SDL.BindGPUVertexStorageBuffers(render_pass, 0, &G.GFX_Renderer.sprite_data_buf, 1) | |
| SDL.BindGPUFragmentSamplers( | |
| render_pass, | |
| 0, | |
| &(SDL.GPUTextureSamplerBinding { | |
| texture = G.GFX_Renderer.texture, | |
| sampler = G.GFX_Renderer.sampler | |
| }), | |
| 1 | |
| ) | |
| SDL.PushGPUVertexUniformData(cmd_buf, 0, &camera_matrix, size_of(matrix[4, 4]f32)) | |
| SDL.DrawGPUPrimitives( | |
| render_pass, | |
| num_vertices = BUDGET_SPRITES * 6, | |
| num_instances = 1, | |
| first_vertex = 0, | |
| first_instance = 0, | |
| ) | |
| SDL.EndGPURenderPass(render_pass) | |
| } | |
| _ = SDL.SubmitGPUCommandBuffer(cmd_buf) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment