Created
April 16, 2025 09:56
-
-
Save p1xelHer0/3ecc3619d3f0d2f51f03d7d71329aa3c to your computer and use it in GitHub Desktop.
Somewhat port of https://github.com/TheSpydog/SDL_gpu_examples/blob/main/Examples/PullSpriteBatch.c
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