Skip to content

Instantly share code, notes, and snippets.

@p1xelHer0
Created April 16, 2025 09:56
Show Gist options
  • Save p1xelHer0/3ecc3619d3f0d2f51f03d7d71329aa3c to your computer and use it in GitHub Desktop.
Save p1xelHer0/3ecc3619d3f0d2f51f03d7d71329aa3c to your computer and use it in GitHub Desktop.
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