bl
This commit is contained in:
parent
41adeeb47f
commit
5b65c4c0e4
11 changed files with 4237 additions and 297 deletions
|
|
@ -37,16 +37,32 @@ GPU_Renderer :: struct {
|
|||
text_transfer_buffer: ^SDL.GPUTransferBuffer,
|
||||
font_texture: ^SDL.GPUTexture,
|
||||
font_sampler: ^SDL.GPUSampler,
|
||||
font_chars: [95]stbtt.bakedchar,
|
||||
font_chars: [95]stbtt.packedchar,
|
||||
font_extra: map[rune]stbtt.packedchar,
|
||||
font_advance: f32,
|
||||
vertices: [dynamic]GPU_Vertex,
|
||||
text_vertices: [dynamic]GPU_Text_Vertex,
|
||||
batches: [dynamic]GPU_Batch,
|
||||
max_vertices: int,
|
||||
max_text_vertices: int,
|
||||
width: int,
|
||||
height: int,
|
||||
}
|
||||
|
||||
// Draw batches preserve submission order across the two pipelines, so text
|
||||
// queued before an opaque rect stays underneath it. Consecutive quads of the
|
||||
// same kind merge into one draw call.
|
||||
GPU_Batch_Kind :: enum {
|
||||
Rect,
|
||||
Text,
|
||||
}
|
||||
|
||||
GPU_Batch :: struct {
|
||||
kind: GPU_Batch_Kind,
|
||||
first: int,
|
||||
count: int,
|
||||
}
|
||||
|
||||
// Compiled SPIR-V is embedded at build time so the binary never depends on
|
||||
// finding shader files at runtime. Regenerate with scripts/compile-shaders.sh
|
||||
// after editing the GLSL sources, then rebuild.
|
||||
|
|
@ -57,7 +73,7 @@ GPU_TEXT_FRAG_SPV :: #load("shaders/compiled/text.frag.spv")
|
|||
|
||||
GPU_MAX_VERTICES :: 240000
|
||||
GPU_MAX_TEXT_VERTICES :: 120000
|
||||
GPU_FONT_ATLAS_SIZE :: 512
|
||||
GPU_FONT_ATLAS_SIZE :: 1024
|
||||
GPU_FONT_PIXEL_HEIGHT :: 15.0
|
||||
GPU_FONT_BASELINE_OFFSET :: 11.0
|
||||
GPU_FONT_CELL_PADDING :: 1.0
|
||||
|
|
@ -226,6 +242,8 @@ gpu_renderer_destroy :: proc(gpu: ^GPU_Renderer) {
|
|||
SDL.DestroyGPUDevice(gpu.device)
|
||||
}
|
||||
delete(gpu.vertices)
|
||||
delete(gpu.batches)
|
||||
delete(gpu.font_extra)
|
||||
delete(gpu.text_vertices)
|
||||
gpu^ = {}
|
||||
}
|
||||
|
|
@ -249,6 +267,7 @@ gpu_create_shader :: proc(device: ^SDL.GPUDevice, name: string, code: []u8, stag
|
|||
gpu_begin :: proc(gpu: ^GPU_Renderer) {
|
||||
clear(&gpu.vertices)
|
||||
clear(&gpu.text_vertices)
|
||||
clear(&gpu.batches)
|
||||
w, h: i32
|
||||
if SDL.GetWindowSize(gpu.window, &w, &h) {
|
||||
gpu.width = int(w)
|
||||
|
|
@ -323,6 +342,35 @@ gpu_text_limited :: proc(gpu: ^GPU_Renderer, x, y: f32, text: string, max_chars:
|
|||
}
|
||||
}
|
||||
|
||||
// Draws a single glyph, returning false when the font has no glyph so the
|
||||
// caller can substitute an ASCII approximation.
|
||||
gpu_rune :: proc(gpu: ^GPU_Renderer, x, y: f32, r: rune, red, green, blue, a: u8) -> bool {
|
||||
if r >= 32 && r < 127 {
|
||||
buf := [1]u8{u8(r)}
|
||||
gpu_text(gpu, x, y, string(buf[:]), red, green, blue, a)
|
||||
return true
|
||||
}
|
||||
glyph, ok := gpu.font_extra[r]
|
||||
if !ok do return false
|
||||
|
||||
glyph_w := f32(glyph.x1 - glyph.x0)
|
||||
glyph_h := f32(glyph.y1 - glyph.y0)
|
||||
if glyph_w <= 0 || glyph_h <= 0 do return true
|
||||
|
||||
xpos := round_f32(x)
|
||||
ypos := y + GPU_FONT_BASELINE_OFFSET
|
||||
x0 := round_f32(xpos + glyph.xoff)
|
||||
y0 := round_f32(ypos + glyph.yoff)
|
||||
x1 := x0 + glyph_w
|
||||
y1 := y0 + glyph_h
|
||||
s0 := f32(glyph.x0) / GPU_FONT_ATLAS_SIZE
|
||||
t0 := f32(glyph.y0) / GPU_FONT_ATLAS_SIZE
|
||||
s1 := f32(glyph.x1) / GPU_FONT_ATLAS_SIZE
|
||||
t1 := f32(glyph.y1) / GPU_FONT_ATLAS_SIZE
|
||||
gpu_push_text_quad(gpu, x0, y0, x1, y1, s0, t0, s1, t1, color_f32(red, green, blue, a))
|
||||
return true
|
||||
}
|
||||
|
||||
gpu_text_width :: proc(text: string) -> int {
|
||||
if len(text) == 0 do return 0
|
||||
width: f32 = 0
|
||||
|
|
@ -415,21 +463,27 @@ gpu_present :: proc(gpu: ^GPU_Renderer) {
|
|||
store_op = .STORE,
|
||||
}
|
||||
pass := SDL.BeginGPURenderPass(command, &target, 1, nil)
|
||||
if vertex_count > 0 {
|
||||
uniforms := GPU_Uniforms{viewport = {f32(gpu.width), f32(gpu.height)}}
|
||||
SDL.PushGPUVertexUniformData(command, 0, &uniforms, size_of(uniforms))
|
||||
SDL.BindGPUGraphicsPipeline(pass, gpu.pipeline)
|
||||
SDL.BindGPUVertexBuffers(pass, 0, &(SDL.GPUBufferBinding{buffer = gpu.vertex_buffer}), 1)
|
||||
SDL.DrawGPUPrimitives(pass, u32(vertex_count), 1, 0, 0)
|
||||
}
|
||||
if text_vertex_count > 0 {
|
||||
uniforms := GPU_Uniforms{viewport = {f32(gpu.width), f32(gpu.height)}}
|
||||
binding := SDL.GPUTextureSamplerBinding{texture = gpu.font_texture, sampler = gpu.font_sampler}
|
||||
SDL.PushGPUVertexUniformData(command, 0, &uniforms, size_of(uniforms))
|
||||
SDL.BindGPUGraphicsPipeline(pass, gpu.text_pipeline)
|
||||
SDL.BindGPUVertexBuffers(pass, 0, &(SDL.GPUBufferBinding{buffer = gpu.text_vertex_buffer}), 1)
|
||||
SDL.BindGPUFragmentSamplers(pass, 0, &binding, 1)
|
||||
SDL.DrawGPUPrimitives(pass, u32(text_vertex_count), 1, 0, 0)
|
||||
uniforms := GPU_Uniforms{viewport = {f32(gpu.width), f32(gpu.height)}}
|
||||
bound := false
|
||||
bound_kind := GPU_Batch_Kind.Rect
|
||||
for batch in gpu.batches {
|
||||
if batch.count == 0 do continue
|
||||
if !bound || bound_kind != batch.kind {
|
||||
SDL.PushGPUVertexUniformData(command, 0, &uniforms, size_of(uniforms))
|
||||
switch batch.kind {
|
||||
case .Rect:
|
||||
SDL.BindGPUGraphicsPipeline(pass, gpu.pipeline)
|
||||
SDL.BindGPUVertexBuffers(pass, 0, &(SDL.GPUBufferBinding{buffer = gpu.vertex_buffer}), 1)
|
||||
case .Text:
|
||||
binding := SDL.GPUTextureSamplerBinding{texture = gpu.font_texture, sampler = gpu.font_sampler}
|
||||
SDL.BindGPUGraphicsPipeline(pass, gpu.text_pipeline)
|
||||
SDL.BindGPUVertexBuffers(pass, 0, &(SDL.GPUBufferBinding{buffer = gpu.text_vertex_buffer}), 1)
|
||||
SDL.BindGPUFragmentSamplers(pass, 0, &binding, 1)
|
||||
}
|
||||
bound = true
|
||||
bound_kind = batch.kind
|
||||
}
|
||||
SDL.DrawGPUPrimitives(pass, u32(batch.count), 1, u32(batch.first), 0)
|
||||
}
|
||||
SDL.EndGPURenderPass(pass)
|
||||
_ = SDL.SubmitGPUCommandBuffer(command)
|
||||
|
|
@ -443,11 +497,68 @@ gpu_create_font_atlas :: proc(gpu: ^GPU_Renderer) -> bool {
|
|||
atlas := make([]u8, atlas_size)
|
||||
defer delete(atlas)
|
||||
|
||||
baked := stbtt.BakeFontBitmap(raw_data(font_bytes), 0, GPU_FONT_PIXEL_HEIGHT, raw_data(atlas), GPU_FONT_ATLAS_SIZE, GPU_FONT_ATLAS_SIZE, 32, len(gpu.font_chars), &gpu.font_chars[0])
|
||||
if baked <= 0 {
|
||||
fmt.println("font bake failed:", font_path)
|
||||
// Extra ranges cover what terminal programs commonly draw: Latin-1,
|
||||
// general punctuation, arrows, box drawing/blocks/geometric shapes,
|
||||
// check marks and braille (spinners).
|
||||
Extra_Range :: struct {
|
||||
first: int,
|
||||
count: int,
|
||||
}
|
||||
extra_ranges := [?]Extra_Range{
|
||||
{0x00A1, 95},
|
||||
{0x2010, 24},
|
||||
{0x2190, 4},
|
||||
{0x2500, 256},
|
||||
{0x2713, 6},
|
||||
{0x2800, 256},
|
||||
}
|
||||
total_extra := 0
|
||||
for r in extra_ranges {
|
||||
total_extra += r.count
|
||||
}
|
||||
extra_chars := make([]stbtt.packedchar, total_extra)
|
||||
defer delete(extra_chars)
|
||||
|
||||
ranges: [1 + len(extra_ranges)]stbtt.pack_range
|
||||
ranges[0] = stbtt.pack_range{
|
||||
font_size = GPU_FONT_PIXEL_HEIGHT,
|
||||
first_unicode_codepoint_in_range = 32,
|
||||
num_chars = i32(len(gpu.font_chars)),
|
||||
chardata_for_range = &gpu.font_chars[0],
|
||||
}
|
||||
offset := 0
|
||||
for r, index in extra_ranges {
|
||||
ranges[index + 1] = stbtt.pack_range{
|
||||
font_size = GPU_FONT_PIXEL_HEIGHT,
|
||||
first_unicode_codepoint_in_range = i32(r.first),
|
||||
num_chars = i32(r.count),
|
||||
chardata_for_range = &extra_chars[offset],
|
||||
}
|
||||
offset += r.count
|
||||
}
|
||||
|
||||
pack: stbtt.pack_context
|
||||
if stbtt.PackBegin(&pack, raw_data(atlas), GPU_FONT_ATLAS_SIZE, GPU_FONT_ATLAS_SIZE, 0, 1, nil) == 0 {
|
||||
fmt.println("font pack failed:", font_path)
|
||||
return false
|
||||
}
|
||||
stbtt.PackSetOversampling(&pack, 1, 1)
|
||||
// Returns 0 when some codepoints are missing from the font; those are
|
||||
// filtered out below via FindGlyphIndex, so partial packs are fine.
|
||||
_ = stbtt.PackFontRanges(&pack, raw_data(font_bytes), 0, &ranges[0], i32(len(ranges)))
|
||||
stbtt.PackEnd(&pack)
|
||||
|
||||
font: stbtt.fontinfo
|
||||
has_font_info := bool(stbtt.InitFont(&font, raw_data(font_bytes), stbtt.GetFontOffsetForIndex(raw_data(font_bytes), 0)))
|
||||
offset = 0
|
||||
for r in extra_ranges {
|
||||
for i in 0 ..< r.count {
|
||||
code := rune(r.first + i)
|
||||
if has_font_info && stbtt.FindGlyphIndex(&font, code) == 0 do continue
|
||||
gpu.font_extra[code] = extra_chars[offset + i]
|
||||
}
|
||||
offset += r.count
|
||||
}
|
||||
widest_advance: f32 = 0
|
||||
for char in gpu.font_chars {
|
||||
glyph_width := char.xoff + f32(char.x1 - char.x0)
|
||||
|
|
@ -518,8 +629,19 @@ gpu_read_font_file :: proc() -> (bytes: []u8, path: string, owned: bool) {
|
|||
return GPU_EMBEDDED_FONT, "embedded NotoSansMono-Regular.ttf", false
|
||||
}
|
||||
|
||||
gpu_batch_current :: proc(gpu: ^GPU_Renderer, kind: GPU_Batch_Kind, first: int) -> ^GPU_Batch {
|
||||
if len(gpu.batches) > 0 {
|
||||
last := &gpu.batches[len(gpu.batches) - 1]
|
||||
if last.kind == kind do return last
|
||||
}
|
||||
append(&gpu.batches, GPU_Batch{kind = kind, first = first})
|
||||
return &gpu.batches[len(gpu.batches) - 1]
|
||||
}
|
||||
|
||||
gpu_push_quad :: proc(gpu: ^GPU_Renderer, x0, y0, x1, y1: f32, c: [4]f32) {
|
||||
if len(gpu.vertices) + 6 > gpu.max_vertices do return
|
||||
batch := gpu_batch_current(gpu, .Rect, len(gpu.vertices))
|
||||
batch.count += 6
|
||||
append(&gpu.vertices, GPU_Vertex{{x0, y0}, c})
|
||||
append(&gpu.vertices, GPU_Vertex{{x1, y0}, c})
|
||||
append(&gpu.vertices, GPU_Vertex{{x0, y1}, c})
|
||||
|
|
@ -530,6 +652,8 @@ gpu_push_quad :: proc(gpu: ^GPU_Renderer, x0, y0, x1, y1: f32, c: [4]f32) {
|
|||
|
||||
gpu_push_text_quad :: proc(gpu: ^GPU_Renderer, x0, y0, x1, y1, s0, t0, s1, t1: f32, c: [4]f32) {
|
||||
if len(gpu.text_vertices) + 6 > gpu.max_text_vertices do return
|
||||
batch := gpu_batch_current(gpu, .Text, len(gpu.text_vertices))
|
||||
batch.count += 6
|
||||
append(&gpu.text_vertices, GPU_Text_Vertex{{x0, y0}, {s0, t0}, c})
|
||||
append(&gpu.text_vertices, GPU_Text_Vertex{{x1, y0}, {s1, t0}, c})
|
||||
append(&gpu.text_vertices, GPU_Text_Vertex{{x0, y1}, {s0, t1}, c})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue