This commit is contained in:
Zachary Levy
2026-04-21 16:16:51 -07:00
parent 7650b90d91
commit ea19b83ba4
5 changed files with 211 additions and 271 deletions

View File

@@ -4,10 +4,6 @@ import "core:log"
import "core:mem"
import sdl "vendor:sdl3"
// ---------------------------------------------------------------------------
// Texture types
// ---------------------------------------------------------------------------
Texture_Id :: distinct u32
INVALID_TEXTURE :: Texture_Id(0) // Slot 0 is reserved/unused
@@ -61,10 +57,6 @@ Texture_Slot :: struct {
// GLOB.pending_texture_releases : [dynamic]Texture_Id
// GLOB.samplers : [SAMPLER_PRESET_COUNT]^sdl.GPUSampler
// ---------------------------------------------------------------------------
// Clay integration type
// ---------------------------------------------------------------------------
Clay_Image_Data :: struct {
texture_id: Texture_Id,
fit: Fit_Mode,
@@ -75,9 +67,9 @@ clay_image_data :: proc(id: Texture_Id, fit: Fit_Mode = .Stretch, tint: Color =
return {texture_id = id, fit = fit, tint = tint}
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Registration -------------
// ---------------------------------------------------------------------------------------------------------------------
// Register a texture. Draw owns the GPU resource and releases it on unregister.
// `data` is tightly-packed row-major bytes matching desc.format.
@@ -236,130 +228,9 @@ update_texture_region :: proc(id: Texture_Id, region: Rectangle, data: []u8) {
}
}
// ---------------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------------
texture_size :: proc(id: Texture_Id) -> [2]u32 {
if id == INVALID_TEXTURE do return {0, 0}
slot := &GLOB.texture_slots[u32(id)]
return {slot.desc.width, slot.desc.height}
}
texture_format :: proc(id: Texture_Id) -> sdl.GPUTextureFormat {
if id == INVALID_TEXTURE do return .INVALID
return GLOB.texture_slots[u32(id)].desc.format
}
texture_kind :: proc(id: Texture_Id) -> Texture_Kind {
if id == INVALID_TEXTURE do return .Static
return GLOB.texture_slots[u32(id)].desc.kind
}
// Internal: get the raw GPU texture pointer for binding during draw.
@(private)
texture_gpu_handle :: proc(id: Texture_Id) -> ^sdl.GPUTexture {
if id == INVALID_TEXTURE do return nil
idx := u32(id)
if idx >= u32(len(GLOB.texture_slots)) do return nil
return GLOB.texture_slots[idx].gpu_texture
}
// ---------------------------------------------------------------------------
// Deferred release (called from draw.end / clear_global)
// ---------------------------------------------------------------------------
@(private)
process_pending_texture_releases :: proc() {
device := GLOB.device
for id in GLOB.pending_texture_releases {
idx := u32(id)
if idx >= u32(len(GLOB.texture_slots)) do continue
slot := &GLOB.texture_slots[idx]
if slot.gpu_texture != nil {
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
slot.gpu_texture = nil
}
slot.generation += 1
append(&GLOB.texture_free_list, idx)
}
clear(&GLOB.pending_texture_releases)
}
// ---------------------------------------------------------------------------
// Sampler pool
// ---------------------------------------------------------------------------
@(private)
get_sampler :: proc(preset: Sampler_Preset) -> ^sdl.GPUSampler {
idx := int(preset)
if GLOB.samplers[idx] != nil do return GLOB.samplers[idx]
// Lazily create
min_filter, mag_filter: sdl.GPUFilter
address_mode: sdl.GPUSamplerAddressMode
switch preset {
case .Nearest_Clamp:
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .CLAMP_TO_EDGE
case .Linear_Clamp:
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .CLAMP_TO_EDGE
case .Nearest_Repeat:
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .REPEAT
case .Linear_Repeat:
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .REPEAT
}
sampler := sdl.CreateGPUSampler(
GLOB.device,
sdl.GPUSamplerCreateInfo {
min_filter = min_filter,
mag_filter = mag_filter,
mipmap_mode = .LINEAR,
address_mode_u = address_mode,
address_mode_v = address_mode,
address_mode_w = address_mode,
},
)
if sampler == nil {
log.errorf("Failed to create sampler preset %v: %s", preset, sdl.GetError())
return GLOB.pipeline_2d_base.sampler // fallback to existing default sampler
}
GLOB.samplers[idx] = sampler
return sampler
}
// Internal: destroy all sampler pool entries. Called from draw.destroy().
@(private)
destroy_sampler_pool :: proc() {
device := GLOB.device
for &s in GLOB.samplers {
if s != nil {
sdl.ReleaseGPUSampler(device, s)
s = nil
}
}
}
// Internal: destroy all registered textures. Called from draw.destroy().
@(private)
destroy_all_textures :: proc() {
device := GLOB.device
for &slot in GLOB.texture_slots {
if slot.gpu_texture != nil {
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
slot.gpu_texture = nil
}
}
delete(GLOB.texture_slots)
delete(GLOB.texture_free_list)
delete(GLOB.pending_texture_releases)
}
// ---------------------------------------------------------------------------
// Fit mode helper
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Helpers -------------
// ---------------------------------------------------------------------------------------------------------------------
// Compute UV rect, recommended sampler, and inner rect for a given fit mode.
// `rect` is the target drawing area; `texture_id` identifies the texture whose
@@ -431,3 +302,113 @@ fit_params :: proc(
return {0, 0, 1, 1}, .Linear_Clamp, inner_rect
}
texture_size :: proc(id: Texture_Id) -> [2]u32 {
if id == INVALID_TEXTURE do return {0, 0}
slot := &GLOB.texture_slots[u32(id)]
return {slot.desc.width, slot.desc.height}
}
texture_format :: proc(id: Texture_Id) -> sdl.GPUTextureFormat {
if id == INVALID_TEXTURE do return .INVALID
return GLOB.texture_slots[u32(id)].desc.format
}
texture_kind :: proc(id: Texture_Id) -> Texture_Kind {
if id == INVALID_TEXTURE do return .Static
return GLOB.texture_slots[u32(id)].desc.kind
}
// Internal: get the raw GPU texture pointer for binding during draw.
@(private)
texture_gpu_handle :: proc(id: Texture_Id) -> ^sdl.GPUTexture {
if id == INVALID_TEXTURE do return nil
idx := u32(id)
if idx >= u32(len(GLOB.texture_slots)) do return nil
return GLOB.texture_slots[idx].gpu_texture
}
// Deferred release (called from draw.end / clear_global)
@(private)
process_pending_texture_releases :: proc() {
device := GLOB.device
for id in GLOB.pending_texture_releases {
idx := u32(id)
if idx >= u32(len(GLOB.texture_slots)) do continue
slot := &GLOB.texture_slots[idx]
if slot.gpu_texture != nil {
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
slot.gpu_texture = nil
}
slot.generation += 1
append(&GLOB.texture_free_list, idx)
}
clear(&GLOB.pending_texture_releases)
}
@(private)
get_sampler :: proc(preset: Sampler_Preset) -> ^sdl.GPUSampler {
idx := int(preset)
if GLOB.samplers[idx] != nil do return GLOB.samplers[idx]
// Lazily create
min_filter, mag_filter: sdl.GPUFilter
address_mode: sdl.GPUSamplerAddressMode
switch preset {
case .Nearest_Clamp:
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .CLAMP_TO_EDGE
case .Linear_Clamp:
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .CLAMP_TO_EDGE
case .Nearest_Repeat:
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .REPEAT
case .Linear_Repeat:
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .REPEAT
}
sampler := sdl.CreateGPUSampler(
GLOB.device,
sdl.GPUSamplerCreateInfo {
min_filter = min_filter,
mag_filter = mag_filter,
mipmap_mode = .LINEAR,
address_mode_u = address_mode,
address_mode_v = address_mode,
address_mode_w = address_mode,
},
)
if sampler == nil {
log.errorf("Failed to create sampler preset %v: %s", preset, sdl.GetError())
return GLOB.pipeline_2d_base.sampler // fallback to existing default sampler
}
GLOB.samplers[idx] = sampler
return sampler
}
// Internal: destroy all sampler pool entries. Called from draw.destroy().
@(private)
destroy_sampler_pool :: proc() {
device := GLOB.device
for &s in GLOB.samplers {
if s != nil {
sdl.ReleaseGPUSampler(device, s)
s = nil
}
}
}
// Internal: destroy all registered textures. Called from draw.destroy().
@(private)
destroy_all_textures :: proc() {
device := GLOB.device
for &slot in GLOB.texture_slots {
if slot.gpu_texture != nil {
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
slot.gpu_texture = nil
}
}
delete(GLOB.texture_slots)
delete(GLOB.texture_free_list)
delete(GLOB.pending_texture_releases)
}