Basic texture support
This commit is contained in:
197
draw/draw.odin
197
draw/draw.odin
@@ -63,15 +63,17 @@ Rectangle :: struct {
|
||||
}
|
||||
|
||||
Sub_Batch_Kind :: enum u8 {
|
||||
Shapes, // non-indexed, white texture, mode 0
|
||||
Shapes, // non-indexed, white texture or user texture, mode 0
|
||||
Text, // indexed, atlas texture, mode 0
|
||||
SDF, // instanced unit quad, white texture, mode 1
|
||||
SDF, // instanced unit quad, white texture or user texture, mode 1
|
||||
}
|
||||
|
||||
Sub_Batch :: struct {
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Shapes: vertex offset; Text: text_batch index; SDF: primitive index
|
||||
count: u32, // Shapes: vertex count; Text: always 1; SDF: primitive count
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Shapes: vertex offset; Text: text_batch index; SDF: primitive index
|
||||
count: u32, // Shapes: vertex count; Text: always 1; SDF: primitive count
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
}
|
||||
|
||||
Layer :: struct {
|
||||
@@ -95,35 +97,60 @@ Scissor :: struct {
|
||||
GLOB: Global
|
||||
|
||||
Global :: struct {
|
||||
odin_context: runtime.Context,
|
||||
pipeline_2d_base: Pipeline_2D_Base,
|
||||
text_cache: Text_Cache,
|
||||
layers: [dynamic]Layer,
|
||||
scissors: [dynamic]Scissor,
|
||||
tmp_shape_verts: [dynamic]Vertex,
|
||||
tmp_text_verts: [dynamic]Vertex,
|
||||
tmp_text_indices: [dynamic]c.int,
|
||||
tmp_text_batches: [dynamic]TextBatch,
|
||||
tmp_primitives: [dynamic]Primitive,
|
||||
tmp_sub_batches: [dynamic]Sub_Batch,
|
||||
tmp_uncached_text: [dynamic]^sdl_ttf.Text, // Uncached TTF_Text objects to destroy after end()
|
||||
clay_memory: [^]u8,
|
||||
msaa_texture: ^sdl.GPUTexture,
|
||||
curr_layer_index: uint,
|
||||
max_layers: int,
|
||||
max_scissors: int,
|
||||
max_shape_verts: int,
|
||||
max_text_verts: int,
|
||||
max_text_indices: int,
|
||||
max_text_batches: int,
|
||||
max_primitives: int,
|
||||
max_sub_batches: int,
|
||||
dpi_scaling: f32,
|
||||
msaa_width: u32,
|
||||
msaa_height: u32,
|
||||
sample_count: sdl.GPUSampleCount,
|
||||
clay_z_index: i16,
|
||||
cleared: bool,
|
||||
// -- Per-frame staging (hottest — touched by every prepare/upload/clear cycle) --
|
||||
tmp_shape_verts: [dynamic]Vertex, // Tessellated shape vertices staged for GPU upload.
|
||||
tmp_text_verts: [dynamic]Vertex, // Text vertices staged for GPU upload.
|
||||
tmp_text_indices: [dynamic]c.int, // Text index buffer staged for GPU upload.
|
||||
tmp_text_batches: [dynamic]TextBatch, // Text atlas batch metadata for indexed drawing.
|
||||
tmp_primitives: [dynamic]Primitive, // SDF primitives staged for GPU storage buffer upload.
|
||||
tmp_sub_batches: [dynamic]Sub_Batch, // Sub-batch records that drive draw call dispatch.
|
||||
tmp_uncached_text: [dynamic]^sdl_ttf.Text, // Uncached TTF_Text objects destroyed after end() submits.
|
||||
layers: [dynamic]Layer, // Draw layers, each with its own scissor stack.
|
||||
scissors: [dynamic]Scissor, // Scissor rects that clip drawing within each layer.
|
||||
|
||||
// -- Per-frame scalars (accessed during prepare and draw_layer) --
|
||||
curr_layer_index: uint, // Index of the currently active layer.
|
||||
dpi_scaling: f32, // Window DPI scale factor applied to all pixel coordinates.
|
||||
clay_z_index: i16, // Tracks z-index for layer splitting during Clay batch processing.
|
||||
cleared: bool, // Whether the render target has been cleared this frame.
|
||||
|
||||
// -- Pipeline (accessed every draw_layer call) --
|
||||
pipeline_2d_base: Pipeline_2D_Base, // The unified 2D GPU pipeline (shaders, buffers, samplers).
|
||||
device: ^sdl.GPUDevice, // GPU device handle, stored at init.
|
||||
samplers: [SAMPLER_PRESET_COUNT]^sdl.GPUSampler, // Lazily-created sampler objects, one per Sampler_Preset.
|
||||
|
||||
// -- Deferred release (processed once per frame at frame boundary) --
|
||||
pending_texture_releases: [dynamic]Texture_Id, // Deferred GPU texture releases, processed next frame.
|
||||
pending_text_releases: [dynamic]^sdl_ttf.Text, // Deferred TTF_Text destroys, processed next frame.
|
||||
|
||||
// -- Textures (registration is occasional, binding is per draw call) --
|
||||
texture_slots: [dynamic]Texture_Slot, // Registered texture slots indexed by Texture_Id.
|
||||
texture_free_list: [dynamic]u32, // Recycled slot indices available for reuse.
|
||||
|
||||
// -- MSAA (once per frame in end()) --
|
||||
msaa_texture: ^sdl.GPUTexture, // Intermediate render target for multi-sample resolve.
|
||||
msaa_width: u32, // Cached width to detect when MSAA texture needs recreation.
|
||||
msaa_height: u32, // Cached height to detect when MSAA texture needs recreation.
|
||||
sample_count: sdl.GPUSampleCount, // Sample count chosen at init (._1 means MSAA disabled).
|
||||
|
||||
// -- Clay (once per frame in prepare_clay_batch) --
|
||||
clay_memory: [^]u8, // Raw memory block backing Clay's internal arena.
|
||||
|
||||
// -- Text (occasional — font registration and text cache lookups) --
|
||||
text_cache: Text_Cache, // Font registry, SDL_ttf engine, and cached TTF_Text objects.
|
||||
|
||||
// -- Resize tracking (cold — checked once per frame in resize_global) --
|
||||
max_layers: int, // High-water marks for dynamic array shrink heuristic.
|
||||
max_scissors: int,
|
||||
max_shape_verts: int,
|
||||
max_text_verts: int,
|
||||
max_text_indices: int,
|
||||
max_text_batches: int,
|
||||
max_primitives: int,
|
||||
max_sub_batches: int,
|
||||
|
||||
// -- Init-only (coldest — set once at init, never written again) --
|
||||
odin_context: runtime.Context, // Odin context captured at init for use in callbacks.
|
||||
}
|
||||
|
||||
Init_Options :: struct {
|
||||
@@ -168,22 +195,30 @@ init :: proc(
|
||||
}
|
||||
|
||||
GLOB = Global {
|
||||
layers = make([dynamic]Layer, 0, INITIAL_LAYER_SIZE, allocator = allocator),
|
||||
scissors = make([dynamic]Scissor, 0, INITIAL_SCISSOR_SIZE, allocator = allocator),
|
||||
tmp_shape_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_indices = make([dynamic]c.int, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_batches = make([dynamic]TextBatch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make([dynamic]Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
odin_context = odin_context,
|
||||
dpi_scaling = sdl.GetWindowDisplayScale(window),
|
||||
clay_memory = make([^]u8, min_memory_size, allocator = allocator),
|
||||
sample_count = resolved_sample_count,
|
||||
pipeline_2d_base = pipeline,
|
||||
text_cache = text_cache,
|
||||
layers = make([dynamic]Layer, 0, INITIAL_LAYER_SIZE, allocator = allocator),
|
||||
scissors = make([dynamic]Scissor, 0, INITIAL_SCISSOR_SIZE, allocator = allocator),
|
||||
tmp_shape_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_indices = make([dynamic]c.int, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_batches = make([dynamic]TextBatch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make([dynamic]Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
device = device,
|
||||
texture_slots = make([dynamic]Texture_Slot, 0, 16, allocator = allocator),
|
||||
texture_free_list = make([dynamic]u32, 0, 16, allocator = allocator),
|
||||
pending_texture_releases = make([dynamic]Texture_Id, 0, 16, allocator = allocator),
|
||||
pending_text_releases = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
odin_context = odin_context,
|
||||
dpi_scaling = sdl.GetWindowDisplayScale(window),
|
||||
clay_memory = make([^]u8, min_memory_size, allocator = allocator),
|
||||
sample_count = resolved_sample_count,
|
||||
pipeline_2d_base = pipeline,
|
||||
text_cache = text_cache,
|
||||
}
|
||||
|
||||
// Reserve slot 0 for INVALID_TEXTURE
|
||||
append(&GLOB.texture_slots, Texture_Slot{})
|
||||
log.debug("Window DPI scaling:", GLOB.dpi_scaling)
|
||||
arena := clay.CreateArenaWithCapacityAndMemory(min_memory_size, GLOB.clay_memory)
|
||||
window_width, window_height: c.int
|
||||
@@ -230,12 +265,23 @@ destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
if GLOB.msaa_texture != nil {
|
||||
sdl.ReleaseGPUTexture(device, GLOB.msaa_texture)
|
||||
}
|
||||
process_pending_texture_releases()
|
||||
destroy_all_textures()
|
||||
destroy_sampler_pool()
|
||||
for ttf_text in GLOB.pending_text_releases do sdl_ttf.DestroyText(ttf_text)
|
||||
delete(GLOB.pending_text_releases)
|
||||
destroy_pipeline_2d_base(device, &GLOB.pipeline_2d_base)
|
||||
destroy_text_cache()
|
||||
}
|
||||
|
||||
// Internal
|
||||
clear_global :: proc() {
|
||||
// Process deferred texture releases from the previous frame
|
||||
process_pending_texture_releases()
|
||||
// Process deferred text releases from the previous frame
|
||||
for ttf_text in GLOB.pending_text_releases do sdl_ttf.DestroyText(ttf_text)
|
||||
clear(&GLOB.pending_text_releases)
|
||||
|
||||
GLOB.curr_layer_index = 0
|
||||
GLOB.clay_z_index = 0
|
||||
GLOB.cleared = false
|
||||
@@ -455,15 +501,24 @@ append_or_extend_sub_batch :: proc(
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32,
|
||||
count: u32,
|
||||
texture_id: Texture_Id = INVALID_TEXTURE,
|
||||
sampler: Sampler_Preset = .Linear_Clamp,
|
||||
) {
|
||||
if scissor.sub_batch_len > 0 {
|
||||
last := &GLOB.tmp_sub_batches[scissor.sub_batch_start + scissor.sub_batch_len - 1]
|
||||
if last.kind == kind && kind != .Text && last.offset + last.count == offset {
|
||||
if last.kind == kind &&
|
||||
kind != .Text &&
|
||||
last.offset + last.count == offset &&
|
||||
last.texture_id == texture_id &&
|
||||
last.sampler == sampler {
|
||||
last.count += count
|
||||
return
|
||||
}
|
||||
}
|
||||
append(&GLOB.tmp_sub_batches, Sub_Batch{kind = kind, offset = offset, count = count})
|
||||
append(
|
||||
&GLOB.tmp_sub_batches,
|
||||
Sub_Batch{kind = kind, offset = offset, count = count, texture_id = texture_id, sampler = sampler},
|
||||
)
|
||||
scissor.sub_batch_len += 1
|
||||
layer.sub_batch_len += 1
|
||||
}
|
||||
@@ -554,6 +609,46 @@ prepare_clay_batch :: proc(
|
||||
)
|
||||
prepare_text(layer, Text{sdl_text, {bounds.x, bounds.y}, color_from_clay(render_data.textColor)})
|
||||
case clay.RenderCommandType.Image:
|
||||
render_data := render_command.renderData.image
|
||||
if render_data.imageData == nil do continue
|
||||
img_data := (^Clay_Image_Data)(render_data.imageData)^
|
||||
cr := render_data.cornerRadius
|
||||
radii := [4]f32{cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}
|
||||
|
||||
// Background color behind the image (Clay allows it)
|
||||
bg := color_from_clay(render_data.backgroundColor)
|
||||
if bg[3] > 0 {
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle(layer, bounds, bg)
|
||||
} else {
|
||||
rectangle_corners(layer, bounds, radii, bg)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute fit UVs
|
||||
uv, sampler, inner := fit_params(img_data.fit, bounds, img_data.texture_id)
|
||||
|
||||
// Draw the image — route by cornerRadius
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle_texture(
|
||||
layer,
|
||||
inner,
|
||||
img_data.texture_id,
|
||||
tint = img_data.tint,
|
||||
uv_rect = uv,
|
||||
sampler = sampler,
|
||||
)
|
||||
} else {
|
||||
rectangle_texture_corners(
|
||||
layer,
|
||||
inner,
|
||||
radii,
|
||||
img_data.texture_id,
|
||||
tint = img_data.tint,
|
||||
uv_rect = uv,
|
||||
sampler = sampler,
|
||||
)
|
||||
}
|
||||
case clay.RenderCommandType.ScissorStart:
|
||||
if bounds.width == 0 || bounds.height == 0 do continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user