Initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*.bin
|
||||||
|
/renderer/res/shaders/compiled/
|
||||||
|
/run.sh
|
||||||
|
/examples.bin.dSYM/
|
||||||
|
/bin/
|
234
examples/main.odin
Normal file
234
examples/main.odin
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "../renderer"
|
||||||
|
import "core:c"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
import clay "library:clay"
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
WINDOW_WIDTH :: 1024
|
||||||
|
WINDOW_HEIGHT :: 728
|
||||||
|
WINDOW_FLAGS :: sdl.WindowFlags{.RESIZABLE, .HIGH_PIXEL_DENSITY}
|
||||||
|
|
||||||
|
window: ^sdl.Window
|
||||||
|
device: ^sdl.GPUDevice
|
||||||
|
debug_enabled := false
|
||||||
|
|
||||||
|
body_text := clay.TextElementConfig {
|
||||||
|
fontId = renderer.JETBRAINS_MONO_REGULAR,
|
||||||
|
fontSize = 44,
|
||||||
|
textColor = { 1.0, 1.0, 1.0, 1.0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
defer destroy()
|
||||||
|
|
||||||
|
when ODIN_DEBUG == true {
|
||||||
|
context.logger = log.create_console_logger(lowest = .Debug)
|
||||||
|
|
||||||
|
//----- Tracking allocator ----------------------------------
|
||||||
|
// Temp
|
||||||
|
track_temp: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track_temp, context.temp_allocator)
|
||||||
|
context.temp_allocator = mem.tracking_allocator(&track_temp)
|
||||||
|
// Default
|
||||||
|
track: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track, context.allocator)
|
||||||
|
context.allocator = mem.tracking_allocator(&track)
|
||||||
|
// Log a warning about any memory that was not freed by the end of the program.
|
||||||
|
// This could be fine for some global state or it could be a memory leak.
|
||||||
|
defer {
|
||||||
|
// Temp allocator
|
||||||
|
if len(track_temp.allocation_map) > 0 {
|
||||||
|
fmt.eprintf(
|
||||||
|
"=== %v allocations not freed - temp allocator: ===\n",
|
||||||
|
len(track_temp.allocation_map),
|
||||||
|
)
|
||||||
|
for _, entry in track_temp.allocation_map {
|
||||||
|
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(track_temp.bad_free_array) > 0 {
|
||||||
|
fmt.eprintf(
|
||||||
|
"=== %v incorrect frees - temp allocator: ===\n",
|
||||||
|
len(track_temp.bad_free_array),
|
||||||
|
)
|
||||||
|
for entry in track_temp.bad_free_array {
|
||||||
|
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mem.tracking_allocator_destroy(&track_temp)
|
||||||
|
// Default allocator
|
||||||
|
if len(track.allocation_map) > 0 {
|
||||||
|
fmt.eprintf(
|
||||||
|
"=== %v allocations not freed - main allocator: ===\n",
|
||||||
|
len(track.allocation_map),
|
||||||
|
)
|
||||||
|
for _, entry in track.allocation_map {
|
||||||
|
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(track.bad_free_array) > 0 {
|
||||||
|
fmt.eprintf(
|
||||||
|
"=== %v incorrect frees - main allocator: ===\n",
|
||||||
|
len(track.bad_free_array),
|
||||||
|
)
|
||||||
|
for entry in track.bad_free_array {
|
||||||
|
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mem.tracking_allocator_destroy(&track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sdl.Init(sdl.InitFlags{.VIDEO}) {
|
||||||
|
log.error("Failed to initialize SDL:", sdl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
window = sdl.CreateWindow("System Controller", WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_FLAGS)
|
||||||
|
|
||||||
|
if window == nil {
|
||||||
|
log.error("Failed to create window:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
device = sdl.CreateGPUDevice(renderer.SHADER_TYPE, true, nil)
|
||||||
|
if device == nil {
|
||||||
|
log.error("Failed to create GPU device:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
driver := sdl.GetGPUDeviceDriver(device)
|
||||||
|
log.info("Created GPU device:", driver)
|
||||||
|
|
||||||
|
if !sdl.ClaimWindowForGPUDevice(device, window) {
|
||||||
|
log.error("Failed to claim GPU device for window:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.init(device, window, WINDOW_WIDTH, WINDOW_HEIGHT, context)
|
||||||
|
|
||||||
|
// debug
|
||||||
|
FPS_REFRESH_INTERVAL :: 1000.0 // 1 second
|
||||||
|
fps_time := sdl.GetTicks()
|
||||||
|
frame_count: int
|
||||||
|
fps: f32
|
||||||
|
|
||||||
|
last_frame_time := sdl.GetTicks()
|
||||||
|
|
||||||
|
program: for {
|
||||||
|
defer free_all(context.temp_allocator)
|
||||||
|
|
||||||
|
// Update debug FPS
|
||||||
|
frame_time := sdl.GetTicks()
|
||||||
|
when ODIN_DEBUG == true {
|
||||||
|
frame_count += 1
|
||||||
|
if frame_time - fps_time >= FPS_REFRESH_INTERVAL {
|
||||||
|
new_fps := f32(frame_count)
|
||||||
|
if new_fps != fps {
|
||||||
|
log.info("FPS:", new_fps)
|
||||||
|
}
|
||||||
|
fps = new_fps
|
||||||
|
frame_count = 0
|
||||||
|
fps_time = frame_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_buffer := sdl.AcquireGPUCommandBuffer(device)
|
||||||
|
if cmd_buffer == nil {
|
||||||
|
log.error("Failed to acquire command buffer")
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if update(cmd_buffer, frame_time - last_frame_time) {
|
||||||
|
log.debug("User command to quit")
|
||||||
|
break program
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(cmd_buffer)
|
||||||
|
|
||||||
|
last_frame_time = frame_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy :: proc() {
|
||||||
|
renderer.destroy(device)
|
||||||
|
sdl.ReleaseWindowFromGPUDevice(device, window)
|
||||||
|
sdl.DestroyWindow(window)
|
||||||
|
sdl.DestroyGPUDevice(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool {
|
||||||
|
frame_time := f32(delta_time) / 1000.0
|
||||||
|
input := input()
|
||||||
|
|
||||||
|
render_cmds: clay.ClayArray(clay.RenderCommand) = layout()
|
||||||
|
|
||||||
|
renderer.prepare(device, window, cmd_buffer, &render_cmds, input.mouse_delta, frame_time)
|
||||||
|
|
||||||
|
return input.should_quit
|
||||||
|
}
|
||||||
|
|
||||||
|
Input :: struct {
|
||||||
|
mouse_delta: [2]f32,
|
||||||
|
should_quit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
input :: proc() -> Input {
|
||||||
|
result := Input{}
|
||||||
|
|
||||||
|
event: sdl.Event
|
||||||
|
for sdl.PollEvent(&event) == true {
|
||||||
|
#partial switch event.type {
|
||||||
|
case .KEY_DOWN:
|
||||||
|
switch event.key.key {
|
||||||
|
case sdl.K_ESCAPE:
|
||||||
|
result.should_quit = true
|
||||||
|
case sdl.K_D:
|
||||||
|
if .LSHIFT in event.key.mod {
|
||||||
|
debug_enabled = !debug_enabled
|
||||||
|
clay.SetDebugModeEnabled(debug_enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .QUIT:
|
||||||
|
result.should_quit = true
|
||||||
|
case .MOUSE_WHEEL:
|
||||||
|
result.mouse_delta[0] = event.wheel.x
|
||||||
|
result.mouse_delta[1] = event.wheel.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
draw :: proc(cmd_buffer: ^sdl.GPUCommandBuffer) {
|
||||||
|
renderer.draw(device, window, cmd_buffer)
|
||||||
|
submit_ok := sdl.SubmitGPUCommandBuffer(cmd_buffer)
|
||||||
|
if !submit_ok {
|
||||||
|
log.debug("Failed to submit command buffer:", sdl.GetError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
|
||||||
|
clay.BeginLayout()
|
||||||
|
|
||||||
|
if clay.UI()(
|
||||||
|
{
|
||||||
|
id = clay.ID("OuterContainer"),
|
||||||
|
layout = {
|
||||||
|
layoutDirection = .TopToBottom,
|
||||||
|
sizing = {clay.SizingGrow({}), clay.SizingGrow({})},
|
||||||
|
childAlignment = {x = .Center, y = .Center},
|
||||||
|
childGap = 16,
|
||||||
|
},
|
||||||
|
backgroundColor = {0.2, 0.2, 0.2, 1.0},
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
clay.Text("3D SCENE", &body_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clay.EndLayout()
|
||||||
|
}
|
||||||
|
|
470
library/clay/clay.odin
Normal file
470
library/clay/clay.odin
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
package clay
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
foreign import Clay "windows/clay.lib"
|
||||||
|
} else when ODIN_OS == .Linux {
|
||||||
|
foreign import Clay "linux/clay.a"
|
||||||
|
} else when ODIN_OS == .Darwin {
|
||||||
|
when ODIN_ARCH == .arm64 {
|
||||||
|
foreign import Clay "macos-arm64/clay.a"
|
||||||
|
} else {
|
||||||
|
foreign import Clay "macos/clay.a"
|
||||||
|
}
|
||||||
|
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
||||||
|
foreign import Clay "wasm/clay.o"
|
||||||
|
}
|
||||||
|
|
||||||
|
String :: struct {
|
||||||
|
isStaticallyAllocated: c.bool,
|
||||||
|
length: c.int32_t,
|
||||||
|
chars: [^]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSlice :: struct {
|
||||||
|
length: c.int32_t,
|
||||||
|
chars: [^]c.char,
|
||||||
|
baseChars: [^]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 :: [2]c.float
|
||||||
|
|
||||||
|
Dimensions :: struct {
|
||||||
|
width: c.float,
|
||||||
|
height: c.float,
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena :: struct {
|
||||||
|
nextAllocation: uintptr,
|
||||||
|
capacity: c.size_t,
|
||||||
|
memory: [^]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundingBox :: struct {
|
||||||
|
x: c.float,
|
||||||
|
y: c.float,
|
||||||
|
width: c.float,
|
||||||
|
height: c.float,
|
||||||
|
}
|
||||||
|
|
||||||
|
Color :: [4]c.float
|
||||||
|
|
||||||
|
CornerRadius :: struct {
|
||||||
|
topLeft: c.float,
|
||||||
|
topRight: c.float,
|
||||||
|
bottomLeft: c.float,
|
||||||
|
bottomRight: c.float,
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderData :: struct {
|
||||||
|
width: u32,
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementId :: struct {
|
||||||
|
id: u32,
|
||||||
|
offset: u32,
|
||||||
|
baseId: u32,
|
||||||
|
stringId: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
EnumBackingType :: u32
|
||||||
|
} else {
|
||||||
|
EnumBackingType :: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommandType :: enum EnumBackingType {
|
||||||
|
None,
|
||||||
|
Rectangle,
|
||||||
|
Border,
|
||||||
|
Text,
|
||||||
|
Image,
|
||||||
|
ScissorStart,
|
||||||
|
ScissorEnd,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
RectangleElementConfig :: struct {
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWrapMode :: enum EnumBackingType {
|
||||||
|
Words,
|
||||||
|
Newlines,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAlignment :: enum EnumBackingType {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextElementConfig :: struct {
|
||||||
|
userData: rawptr,
|
||||||
|
textColor: Color,
|
||||||
|
fontId: u16,
|
||||||
|
fontSize: u16,
|
||||||
|
letterSpacing: u16,
|
||||||
|
lineHeight: u16,
|
||||||
|
wrapMode: TextWrapMode,
|
||||||
|
textAlignment: TextAlignment,
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageElementConfig :: struct {
|
||||||
|
imageData: rawptr,
|
||||||
|
sourceDimensions: Dimensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomElementConfig :: struct {
|
||||||
|
customData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderWidth :: struct {
|
||||||
|
left: u16,
|
||||||
|
right: u16,
|
||||||
|
top: u16,
|
||||||
|
bottom: u16,
|
||||||
|
betweenChildren: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderElementConfig :: struct {
|
||||||
|
color: Color,
|
||||||
|
width: BorderWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollElementConfig :: struct {
|
||||||
|
horizontal: bool,
|
||||||
|
vertical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingAttachPointType :: enum EnumBackingType {
|
||||||
|
LeftTop,
|
||||||
|
LeftCenter,
|
||||||
|
LeftBottom,
|
||||||
|
CenterTop,
|
||||||
|
CenterCenter,
|
||||||
|
CenterBottom,
|
||||||
|
RightTop,
|
||||||
|
RightCenter,
|
||||||
|
RightBottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingAttachPoints :: struct {
|
||||||
|
element: FloatingAttachPointType,
|
||||||
|
parent: FloatingAttachPointType,
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerCaptureMode :: enum EnumBackingType {
|
||||||
|
Capture,
|
||||||
|
Passthrough,
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingAttachToElement :: enum EnumBackingType {
|
||||||
|
None,
|
||||||
|
Parent,
|
||||||
|
ElementWithId,
|
||||||
|
Root,
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingElementConfig :: struct {
|
||||||
|
offset: Vector2,
|
||||||
|
expand: Dimensions,
|
||||||
|
parentId: u32,
|
||||||
|
zIndex: i16,
|
||||||
|
attachment: FloatingAttachPoints,
|
||||||
|
pointerCaptureMode: PointerCaptureMode,
|
||||||
|
attachTo: FloatingAttachToElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextRenderData :: struct {
|
||||||
|
stringContents: StringSlice,
|
||||||
|
textColor: Color,
|
||||||
|
fontId: u16,
|
||||||
|
fontSize: u16,
|
||||||
|
letterSpacing: u16,
|
||||||
|
lineHeight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
RectangleRenderData :: struct {
|
||||||
|
backgroundColor: Color,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageRenderData :: struct {
|
||||||
|
backgroundColor: Color,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
|
sourceDimensions: Dimensions,
|
||||||
|
imageData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRenderData :: struct {
|
||||||
|
backgroundColor: Color,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
|
customData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderRenderData :: struct {
|
||||||
|
color: Color,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
|
width: BorderWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommandData :: struct #raw_union {
|
||||||
|
rectangle: RectangleRenderData,
|
||||||
|
text: TextRenderData,
|
||||||
|
image: ImageRenderData,
|
||||||
|
custom: CustomRenderData,
|
||||||
|
border: BorderRenderData,
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommand :: struct {
|
||||||
|
boundingBox: BoundingBox,
|
||||||
|
renderData: RenderCommandData,
|
||||||
|
userData: rawptr,
|
||||||
|
id: u32,
|
||||||
|
zIndex: i16,
|
||||||
|
commandType: RenderCommandType,
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollContainerData :: struct {
|
||||||
|
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
|
||||||
|
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
|
||||||
|
scrollPosition: ^Vector2,
|
||||||
|
scrollContainerDimensions: Dimensions,
|
||||||
|
contentDimensions: Dimensions,
|
||||||
|
config: ScrollElementConfig,
|
||||||
|
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
|
||||||
|
found: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementData :: struct {
|
||||||
|
boundingBox: BoundingBox,
|
||||||
|
found: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerDataInteractionState :: enum EnumBackingType {
|
||||||
|
PressedThisFrame,
|
||||||
|
Pressed,
|
||||||
|
ReleasedThisFrame,
|
||||||
|
Released,
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerData :: struct {
|
||||||
|
position: Vector2,
|
||||||
|
state: PointerDataInteractionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingType :: enum EnumBackingType {
|
||||||
|
Fit,
|
||||||
|
Grow,
|
||||||
|
Percent,
|
||||||
|
Fixed,
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingConstraintsMinMax :: struct {
|
||||||
|
min: c.float,
|
||||||
|
max: c.float,
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingConstraints :: struct #raw_union {
|
||||||
|
sizeMinMax: SizingConstraintsMinMax,
|
||||||
|
sizePercent: c.float,
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingAxis :: struct {
|
||||||
|
// Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions
|
||||||
|
constraints: SizingConstraints,
|
||||||
|
type: SizingType,
|
||||||
|
}
|
||||||
|
|
||||||
|
Sizing :: struct {
|
||||||
|
width: SizingAxis,
|
||||||
|
height: SizingAxis,
|
||||||
|
}
|
||||||
|
|
||||||
|
Padding :: struct {
|
||||||
|
left: u16,
|
||||||
|
right: u16,
|
||||||
|
top: u16,
|
||||||
|
bottom: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutDirection :: enum EnumBackingType {
|
||||||
|
LeftToRight,
|
||||||
|
TopToBottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutAlignmentX :: enum EnumBackingType {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutAlignmentY :: enum EnumBackingType {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
ChildAlignment :: struct {
|
||||||
|
x: LayoutAlignmentX,
|
||||||
|
y: LayoutAlignmentY,
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutConfig :: struct {
|
||||||
|
sizing: Sizing,
|
||||||
|
padding: Padding,
|
||||||
|
childGap: u16,
|
||||||
|
childAlignment: ChildAlignment,
|
||||||
|
layoutDirection: LayoutDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
ClayArray :: struct($type: typeid) {
|
||||||
|
capacity: i32,
|
||||||
|
length: i32,
|
||||||
|
internalArray: [^]type,
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementDeclaration :: struct {
|
||||||
|
id: ElementId,
|
||||||
|
layout: LayoutConfig,
|
||||||
|
backgroundColor: Color,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
|
image: ImageElementConfig,
|
||||||
|
floating: FloatingElementConfig,
|
||||||
|
custom: CustomElementConfig,
|
||||||
|
scroll: ScrollElementConfig,
|
||||||
|
border: BorderElementConfig,
|
||||||
|
userData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorType :: enum EnumBackingType {
|
||||||
|
TextMeasurementFunctionNotProvided,
|
||||||
|
ArenaCapacityExceeded,
|
||||||
|
ElementsCapacityExceeded,
|
||||||
|
TextMeasurementCapacityExceeded,
|
||||||
|
DuplicateId,
|
||||||
|
FloatingContainerParentNotFound,
|
||||||
|
PercentageOver1,
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorData :: struct {
|
||||||
|
errorType: ErrorType,
|
||||||
|
errorText: String,
|
||||||
|
userData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorHandler :: struct {
|
||||||
|
handler: proc "c" (errorData: ErrorData),
|
||||||
|
userData: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
Context :: struct {} // opaque structure, only use as a pointer
|
||||||
|
|
||||||
|
@(link_prefix = "Clay_", default_calling_convention = "c")
|
||||||
|
foreign Clay {
|
||||||
|
_OpenElement :: proc() ---
|
||||||
|
_CloseElement :: proc() ---
|
||||||
|
MinMemorySize :: proc() -> u32 ---
|
||||||
|
CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
|
||||||
|
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
|
||||||
|
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context ---
|
||||||
|
GetCurrentContext :: proc() -> ^Context ---
|
||||||
|
SetCurrentContext :: proc(ctx: ^Context) ---
|
||||||
|
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
|
||||||
|
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
|
||||||
|
BeginLayout :: proc() ---
|
||||||
|
EndLayout :: proc() -> ClayArray(RenderCommand) ---
|
||||||
|
GetElementId :: proc(id: String) -> ElementId ---
|
||||||
|
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
|
||||||
|
GetElementData :: proc(id: ElementId) -> ElementData ---
|
||||||
|
Hovered :: proc() -> bool ---
|
||||||
|
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
|
||||||
|
PointerOver :: proc(id: ElementId) -> bool ---
|
||||||
|
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
|
||||||
|
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
|
||||||
|
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
|
||||||
|
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
|
||||||
|
SetDebugModeEnabled :: proc(enabled: bool) ---
|
||||||
|
IsDebugModeEnabled :: proc() -> bool ---
|
||||||
|
SetCullingEnabled :: proc(enabled: bool) ---
|
||||||
|
GetMaxElementCount :: proc() -> i32 ---
|
||||||
|
SetMaxElementCount :: proc(maxElementCount: i32) ---
|
||||||
|
GetMaxMeasureTextCacheWordCount :: proc() -> i32 ---
|
||||||
|
SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) ---
|
||||||
|
ResetMeasureTextCache :: proc() ---
|
||||||
|
}
|
||||||
|
|
||||||
|
@(link_prefix = "Clay_", default_calling_convention = "c", private)
|
||||||
|
foreign Clay {
|
||||||
|
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
|
||||||
|
_HashString :: proc(key: String, offset: u32, seed: u32) -> ElementId ---
|
||||||
|
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
|
||||||
|
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
|
||||||
|
_GetParentElementId :: proc() -> u32 ---
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool {
|
||||||
|
_ConfigureOpenElement(config)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@(deferred_none = _CloseElement)
|
||||||
|
UI :: proc() -> proc (config: ElementDeclaration) -> bool {
|
||||||
|
_OpenElement()
|
||||||
|
return ConfigureOpenElement
|
||||||
|
}
|
||||||
|
|
||||||
|
Text :: proc($text: string, config: ^TextElementConfig) {
|
||||||
|
wrapped := MakeString(text)
|
||||||
|
wrapped.isStaticallyAllocated = true
|
||||||
|
_OpenTextElement(wrapped, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
|
||||||
|
_OpenTextElement(MakeString(text), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
|
||||||
|
return _StoreTextElementConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
PaddingAll :: proc(allPadding: u16) -> Padding {
|
||||||
|
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
|
||||||
|
}
|
||||||
|
|
||||||
|
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
|
||||||
|
return CornerRadius{radius, radius, radius, radius}
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||||
|
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||||
|
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingFixed :: proc(size: c.float) -> SizingAxis {
|
||||||
|
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
|
||||||
|
return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}}
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeString :: proc(label: string) -> String {
|
||||||
|
return String{chars = raw_data(label), length = cast(c.int)len(label)}
|
||||||
|
}
|
||||||
|
|
||||||
|
ID :: proc(label: string, index: u32 = 0) -> ElementId {
|
||||||
|
return _HashString(MakeString(label), index, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
|
||||||
|
return _HashString(MakeString(label), index, _GetParentElementId())
|
||||||
|
}
|
BIN
library/clay/linux-arm64/clay.a
Normal file
BIN
library/clay/linux-arm64/clay.a
Normal file
Binary file not shown.
BIN
library/clay/linux/clay.a
Normal file
BIN
library/clay/linux/clay.a
Normal file
Binary file not shown.
BIN
library/clay/macos-arm64/clay.a
Normal file
BIN
library/clay/macos-arm64/clay.a
Normal file
Binary file not shown.
BIN
library/clay/macos/clay.a
Normal file
BIN
library/clay/macos/clay.a
Normal file
Binary file not shown.
BIN
library/clay/wasm/clay.o
Normal file
BIN
library/clay/wasm/clay.o
Normal file
Binary file not shown.
BIN
library/clay/windows/clay.lib
Normal file
BIN
library/clay/windows/clay.lib
Normal file
Binary file not shown.
184
library/sdl3_ttf/sdl3_ttf.odin
Normal file
184
library/sdl3_ttf/sdl3_ttf.odin
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package sdl3_ttf
|
||||||
|
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
import "core:c"
|
||||||
|
|
||||||
|
foreign import lib "system:SDL3_ttf"
|
||||||
|
|
||||||
|
Font :: struct {}
|
||||||
|
|
||||||
|
Text :: struct {
|
||||||
|
text: cstring,
|
||||||
|
num_lines: c.int,
|
||||||
|
refcount: c.int,
|
||||||
|
internal: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEngine :: struct {}
|
||||||
|
|
||||||
|
Direction :: enum c.int {
|
||||||
|
LTR = 0,
|
||||||
|
RTL,
|
||||||
|
TTB,
|
||||||
|
BTT,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal == empty
|
||||||
|
FontStyleFlag :: enum u32 {
|
||||||
|
BOLD = 0,
|
||||||
|
ITALIC = 1,
|
||||||
|
UNDERLINE = 2,
|
||||||
|
STRIKETHROUGH = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
FontStyleFlags :: bit_set[FontStyleFlag;u32]
|
||||||
|
FONT_STYLE_NORMAL :: FontStyleFlags{}
|
||||||
|
FONT_STYLE_BOLD :: FontStyleFlags{.BOLD}
|
||||||
|
FONT_STYLE_ITALIC :: FontStyleFlags{.ITALIC}
|
||||||
|
FONT_STYLE_UNDERLINE :: FontStyleFlags{.UNDERLINE}
|
||||||
|
FONT_STYLE_STRIKETHROUGH :: FontStyleFlags{.STRIKETHROUGH}
|
||||||
|
|
||||||
|
HintingFlags :: enum c.int {
|
||||||
|
NORMAL = 0,
|
||||||
|
LIGHT,
|
||||||
|
MONO,
|
||||||
|
NONE,
|
||||||
|
LIGHT_SUBPIXEL,
|
||||||
|
}
|
||||||
|
|
||||||
|
TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER :: "SDL_ttf.font.outline.line_cap"
|
||||||
|
TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER :: "SDL_ttf.font.outline.line_join"
|
||||||
|
TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER :: "SDL_ttf.font.outline.miter_limit"
|
||||||
|
|
||||||
|
HorizontalAlignment :: enum c.int {
|
||||||
|
INVALID = -1,
|
||||||
|
LEFT,
|
||||||
|
CENTER,
|
||||||
|
RIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUAtlasDrawSequence :: struct {
|
||||||
|
atlas_texture: ^sdl.GPUTexture,
|
||||||
|
vertex_positions: [^]sdl.FPoint,
|
||||||
|
uvs: [^]sdl.FPoint, // Normalized
|
||||||
|
num_verticies: c.int,
|
||||||
|
indices: [^]c.int,
|
||||||
|
num_indices: c.int,
|
||||||
|
next: ^GPUAtlasDrawSequence, // If nil, this is the last text in the sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUTextEngineWinding :: enum c.int {
|
||||||
|
INVALID = -1,
|
||||||
|
CLOCKWISE,
|
||||||
|
COUNTERCLOCKWISE,
|
||||||
|
}
|
||||||
|
|
||||||
|
SubStringFlag :: enum u32 {
|
||||||
|
TEXT_START,
|
||||||
|
LINE_START,
|
||||||
|
LINE_END,
|
||||||
|
TEXT_END,
|
||||||
|
}
|
||||||
|
|
||||||
|
SubString :: struct {
|
||||||
|
flags: SubStringFlag,
|
||||||
|
offset: c.int,
|
||||||
|
length: c.int,
|
||||||
|
line_index: c.int,
|
||||||
|
cluster_index: c.int,
|
||||||
|
rect: sdl.Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// General
|
||||||
|
@(default_calling_convention = "c", link_prefix = "TTF_")
|
||||||
|
foreign lib {
|
||||||
|
Init :: proc() -> bool ---
|
||||||
|
CreateGPUTextEngine :: proc(device: ^sdl.GPUDevice) -> ^TextEngine ---
|
||||||
|
DestroyGPUTextEngine :: proc(engine: ^TextEngine) ---
|
||||||
|
Quit :: proc() ---
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fonts
|
||||||
|
@(default_calling_convention = "c", link_prefix = "TTF_")
|
||||||
|
foreign lib {
|
||||||
|
CloseFont :: proc(font: ^Font) ---
|
||||||
|
FontHasGlyph :: proc(font: ^Font, glyph: u32) -> bool ---
|
||||||
|
FontIsFixedWidth :: proc(font: ^Font) -> bool ---
|
||||||
|
GetFontAscent :: proc(font: ^Font) -> c.int ---
|
||||||
|
GetFontDescent :: proc(font: ^Font) -> c.int ---
|
||||||
|
GetFontDirection :: proc(font: ^Font) -> Direction ---
|
||||||
|
GetFontDPI :: proc(font: ^Font, hdpi: ^c.int, vdpi: ^c.int) -> bool ---
|
||||||
|
GetFontFamilyName :: proc(font: ^Font) -> cstring ---
|
||||||
|
GetFontGeneration :: proc(font: ^Font) -> u32 ---
|
||||||
|
GetFontHeight :: proc(font: ^Font) -> c.int ---
|
||||||
|
GetFontHinting :: proc(font: ^Font) -> HintingFlags ---
|
||||||
|
GetFontKerning :: proc(font: ^Font) -> bool ---
|
||||||
|
/// Returns the font's recommended spacing
|
||||||
|
GetFontLineSkip :: proc(font: ^Font) -> c.int ---
|
||||||
|
GetFontOutline :: proc(font: ^Font) -> c.int ---
|
||||||
|
GetFontProperties :: proc(font: ^Font) -> sdl.PropertiesID ---
|
||||||
|
GetFontSize :: proc(font: ^Font) -> f32 ---
|
||||||
|
GetFontStyle :: proc(font: ^Font) -> FontStyleFlags ---
|
||||||
|
GetFontStyleName :: proc(font: ^Font) -> cstring ---
|
||||||
|
GetFontWrapAlignment :: proc(font: ^Font) -> HorizontalAlignment ---
|
||||||
|
GetFreeTypeVersion :: proc(major: ^c.int, minor: ^c.int, patch: ^c.int) ---
|
||||||
|
GetGlyphMetrics :: proc(font: ^Font, glyph: u32, min_x: ^c.int, max_x: ^c.int, min_y: ^c.int, max_y: ^c.int, advance: ^c.int) -> bool ---
|
||||||
|
GetGlyphScript :: proc(glyph: u32, script: ^c.char, script_size: c.size_t) -> bool ---
|
||||||
|
/// `stream`: A `sdl.IOStream` to provide a font's file data
|
||||||
|
/// `close_io`: Close src when the font is closed, false to leave it open
|
||||||
|
/// `point_size`: Font point size to use for the newly-opened font
|
||||||
|
OpenFontIO :: proc(stream: ^sdl.IOStream, close_io: bool, point_size: f32) -> ^Font ---
|
||||||
|
OpenFont :: proc(file: cstring, point_size: f32) -> ^Font ---
|
||||||
|
SetFontDirection :: proc(font: ^Font, direction: Direction) -> bool ---
|
||||||
|
SetFontHinting :: proc(font: ^Font, hinting_flags: HintingFlags) ---
|
||||||
|
SetFontKerning :: proc(font: ^Font, enabled: bool) ---
|
||||||
|
SetFontLineSkip :: proc(font: ^Font, lineskip: c.int) ---
|
||||||
|
SetFontOutline :: proc(font: ^Font, outline: c.int) -> bool ---
|
||||||
|
SetFontScript :: proc(font: ^Font, script: cstring) -> bool ---
|
||||||
|
SetFontSize :: proc(font: ^Font, pt_size: f32) -> bool ---
|
||||||
|
SetFontSizeDPI :: proc(font: ^Font, pt_size: f32, hdpi: c.int, vdpi: c.int) -> bool ---
|
||||||
|
SetFontStyle :: proc(font: ^Font, style: FontStyleFlags) ---
|
||||||
|
SetFontWrapAlignment :: proc(font: ^Font, horizontal_alignment: HorizontalAlignment) ---
|
||||||
|
SetGPUTextEngineWinding :: proc(engine: ^TextEngine, winding: GPUTextEngineWinding) ---
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text
|
||||||
|
@(default_calling_convention = "c", link_prefix = "TTF_")
|
||||||
|
foreign lib {
|
||||||
|
AppendTextString :: proc(text: ^Text, str: cstring, length: c.size_t) -> bool ---
|
||||||
|
CreateText :: proc(engine: ^TextEngine, font: ^Font, text: cstring, length: c.size_t) -> ^Text ---
|
||||||
|
DeleteTextString :: proc(text: ^Text, offset: c.int, length: c.int) -> bool ---
|
||||||
|
DestroyText :: proc(text: ^Text) ---
|
||||||
|
GetGPUTextDrawData :: proc(text: ^Text) -> ^GPUAtlasDrawSequence ---
|
||||||
|
GetGPUTextEngineWinding :: proc(engine: ^TextEngine) -> GPUTextEngineWinding ---
|
||||||
|
GetNextTextSubString :: proc(text: ^Text, substring: ^SubString, next: ^SubString) -> bool ---
|
||||||
|
GetPreviousTextSubString :: proc(text: ^Text, substring: ^SubString, previous: ^SubString) -> bool ---
|
||||||
|
/// Calculate the dimensions of a rendered string of UTF-8 text.
|
||||||
|
GetStringSize :: proc(font: ^Font, text: cstring, length: c.size_t, w: ^c.int, h: ^c.int) -> bool ---
|
||||||
|
GetStringSizeWrapped :: proc(font: ^Font, text: cstring, length: c.size_t, wrap_width: c.int, w: ^c.int, h: ^c.int) -> bool ---
|
||||||
|
GetTextColor :: proc(text: ^Text, r: ^u8, g: ^u8, b: ^u8, a: ^u8) -> bool ---
|
||||||
|
GetTextColorFloat :: proc(text: ^Text, r: ^f32, g: ^f32, b: ^f32, a: ^f32) -> bool ---
|
||||||
|
GetTextEngine :: proc(text: ^Text) -> ^TextEngine ---
|
||||||
|
GetTextFont :: proc(text: ^Text) -> ^Font ---
|
||||||
|
GetTextPosition :: proc(text: ^Text, x: ^c.int, y: ^c.int) -> bool ---
|
||||||
|
GetTextProperties :: proc(text: ^Text) -> sdl.PropertiesID ---
|
||||||
|
GetTextSize :: proc(text: ^Text, width: ^c.int, height: ^c.int) -> bool ---
|
||||||
|
GetTextSubString :: proc(text: ^Text, offset: c.int, substring: ^SubString) -> bool ---
|
||||||
|
GetTextSubStringForLine :: proc(text: ^Text, line: c.int, substring: ^SubString) -> bool ---
|
||||||
|
GetTextSubStringForPoint :: proc(text: ^Text, x: c.int, y: c.int, substring: ^SubString) -> bool ---
|
||||||
|
GetTextSubStringsForRange :: proc(text: ^Text, offset: c.int, length: c.int, count: ^c.int) -> [^]^SubString ---
|
||||||
|
GetTextWrapping :: proc(text: ^Text, wrap_length: ^c.int) -> bool ---
|
||||||
|
GetTextWrapWidth :: proc(text: ^Text, wrap_width: ^c.int) -> bool ---
|
||||||
|
InsertTextString :: proc(text: ^Text, offset: c.int, str: cstring, length: c.size_t) -> bool ---
|
||||||
|
// Calculate how much of a UTF-8 string will fit in a given width.
|
||||||
|
MeasureString :: proc(font: ^Font, text: cstring, length: c.size_t, max_width: c.int, measured_width: ^c.int, measured_length: ^c.size_t) -> bool ---
|
||||||
|
SetTextColor :: proc(text: ^Text, r: u8, g: u8, b: u8, a: u8) -> bool ---
|
||||||
|
SetTextColorFloat :: proc(text: ^Text, r: f32, g: f32, b: f32, a: f32) -> bool ---
|
||||||
|
SetTextEngine :: proc(text: ^Text, engine: ^TextEngine) -> bool ---
|
||||||
|
SetTextFont :: proc(text: ^Text, font: ^Font) -> bool ---
|
||||||
|
SetTextPosition :: proc(text: ^Text, x: c.int, y: c.int) -> bool ---
|
||||||
|
SetTextString :: proc(text: ^Text, str: cstring, length: c.size_t) -> bool ---
|
||||||
|
SetTextWrapping :: proc(text: ^Text, wrap_length: c.int) -> bool ---
|
||||||
|
SetTextWrapWhitespaceVisible :: proc(text: ^Text, visible: bool) -> bool ---
|
||||||
|
SetTextWrapWidth :: proc(text: ^Text, wrap_width: c.int) -> bool ---
|
||||||
|
}
|
51
renderer/buffer.odin
Normal file
51
renderer/buffer.odin
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package renderer
|
||||||
|
|
||||||
|
import "core:log"
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
Buffer :: struct {
|
||||||
|
gpu: ^sdl.GPUBuffer,
|
||||||
|
transfer: ^sdl.GPUTransferBuffer,
|
||||||
|
size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
create_buffer :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
size: u32,
|
||||||
|
gpu_usage: sdl.GPUBufferUsageFlags,
|
||||||
|
) -> Buffer {
|
||||||
|
return Buffer {
|
||||||
|
gpu = sdl.CreateGPUBuffer(device, sdl.GPUBufferCreateInfo{usage = gpu_usage, size = size}),
|
||||||
|
transfer = sdl.CreateGPUTransferBuffer(
|
||||||
|
device,
|
||||||
|
sdl.GPUTransferBufferCreateInfo{usage = .UPLOAD, size = size},
|
||||||
|
),
|
||||||
|
size = size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resize_buffer :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
buffer: ^Buffer,
|
||||||
|
new_size: u32,
|
||||||
|
gpu_usage: sdl.GPUBufferUsageFlags,
|
||||||
|
) {
|
||||||
|
if new_size > buffer.size {
|
||||||
|
log.debug("Resizing buffer from", buffer.size, "to", new_size)
|
||||||
|
destroy_buffer(device, buffer)
|
||||||
|
buffer.gpu = sdl.CreateGPUBuffer(
|
||||||
|
device,
|
||||||
|
sdl.GPUBufferCreateInfo{usage = gpu_usage, size = new_size},
|
||||||
|
)
|
||||||
|
buffer.transfer = sdl.CreateGPUTransferBuffer(
|
||||||
|
device,
|
||||||
|
sdl.GPUTransferBufferCreateInfo{usage = .UPLOAD, size = new_size},
|
||||||
|
)
|
||||||
|
buffer.size = new_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_buffer :: proc(device: ^sdl.GPUDevice, buffer: ^Buffer) {
|
||||||
|
sdl.ReleaseGPUBuffer(device, buffer.gpu)
|
||||||
|
sdl.ReleaseGPUTransferBuffer(device, buffer.transfer)
|
||||||
|
}
|
35
renderer/compile_shaders.sh
Executable file
35
renderer/compile_shaders.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
if ! command -v glslangValidator 2>&1 > /dev/null
|
||||||
|
then
|
||||||
|
echo "glslangValidator not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v spirv-cross 2>&1 > /dev/null
|
||||||
|
then
|
||||||
|
echo "spirv-cross not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert GLSL to SPIRV
|
||||||
|
echo "Converting GLSL shaders to SPIRV..."
|
||||||
|
mkdir -p renderer/res/shaders/compiled
|
||||||
|
cd renderer/res/shaders/raw || exit
|
||||||
|
glslangValidator -V quad.vert -o ../compiled/quad.vert.spv
|
||||||
|
glslangValidator -V quad.frag -o ../compiled/quad.frag.spv
|
||||||
|
glslangValidator -V text.vert -o ../compiled/text.vert.spv
|
||||||
|
glslangValidator -V text.frag -o ../compiled/text.frag.spv
|
||||||
|
glslangValidator -V scene.vert -o ../compiled/scene.vert.spv
|
||||||
|
glslangValidator -V scene.frag -o ../compiled/scene.frag.spv
|
||||||
|
|
||||||
|
# Convert SPIRV to MSL
|
||||||
|
echo "Done converting GLSL to SPIRV. Converting SPIRV to MSL..."
|
||||||
|
cd ../compiled || exit
|
||||||
|
spirv-cross --msl quad.vert.spv --output quad.vert.metal
|
||||||
|
spirv-cross --msl quad.frag.spv --output quad.frag.metal
|
||||||
|
spirv-cross --msl text.vert.spv --output text.vert.metal
|
||||||
|
spirv-cross --msl text.frag.spv --output text.frag.metal
|
||||||
|
spirv-cross --msl scene.vert.spv --output scene.vert.metal
|
||||||
|
spirv-cross --msl scene.frag.spv --output scene.frag.metal
|
||||||
|
|
||||||
|
echo "Done processing shaders."
|
243
renderer/quad.odin
Normal file
243
renderer/quad.odin
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package renderer
|
||||||
|
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
tmp_quads: [dynamic]Quad
|
||||||
|
|
||||||
|
QuadPipeline :: struct {
|
||||||
|
instance_buffer: Buffer,
|
||||||
|
num_instances: u32,
|
||||||
|
sdl_pipeline: ^sdl.GPUGraphicsPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
Quad :: struct {
|
||||||
|
position_scale: [4]f32,
|
||||||
|
corner_radii: [4]f32,
|
||||||
|
color: [4]f32,
|
||||||
|
border_color: [4]f32,
|
||||||
|
border_width: f32,
|
||||||
|
_: [3]f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> QuadPipeline {
|
||||||
|
log.debug("Creating quad pipeline")
|
||||||
|
when ODIN_OS == .Darwin {
|
||||||
|
vert_raw := #load("res/shaders/compiled/quad.vert.metal")
|
||||||
|
frag_raw := #load("res/shaders/compiled/quad.frag.metal")
|
||||||
|
} else {
|
||||||
|
vert_raw := #load("res/shaders/compiled/quad.vert.spv")
|
||||||
|
frag_raw := #load("res/shaders/compiled/quad.frag.spv")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Loaded", len(vert_raw), "vert bytes")
|
||||||
|
log.debug("Loaded", len(frag_raw), "frag bytes")
|
||||||
|
log.debug("ShaderType:", SHADER_TYPE)
|
||||||
|
|
||||||
|
|
||||||
|
vert_info := sdl.GPUShaderCreateInfo {
|
||||||
|
code_size = len(vert_raw),
|
||||||
|
code = raw_data(vert_raw),
|
||||||
|
entrypoint = ENTRY_POINT,
|
||||||
|
format = SHADER_TYPE,
|
||||||
|
stage = sdl.GPUShaderStage.VERTEX,
|
||||||
|
num_uniform_buffers = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
frag_info := sdl.GPUShaderCreateInfo {
|
||||||
|
code_size = len(frag_raw),
|
||||||
|
code = raw_data(frag_raw),
|
||||||
|
entrypoint = ENTRY_POINT,
|
||||||
|
format = SHADER_TYPE,
|
||||||
|
stage = sdl.GPUShaderStage.FRAGMENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
vert_shader := sdl.CreateGPUShader(device, vert_info)
|
||||||
|
if vert_shader == nil {
|
||||||
|
log.error("Could not create vertex shader:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
frag_shader := sdl.CreateGPUShader(device, frag_info)
|
||||||
|
if frag_shader == nil {
|
||||||
|
log.error("Could not create fragment shader:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_attributes: [5]sdl.GPUVertexAttribute = {
|
||||||
|
// position and scale
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 0,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = 0,
|
||||||
|
},
|
||||||
|
// corner radii
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 1,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = size_of(f32) * 4,
|
||||||
|
},
|
||||||
|
// color
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 2,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = size_of(f32) * 8,
|
||||||
|
},
|
||||||
|
// border color
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 3,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = size_of(f32) * 12,
|
||||||
|
},
|
||||||
|
// border width
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 4,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT,
|
||||||
|
offset = size_of(f32) * 16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline_info := sdl.GPUGraphicsPipelineCreateInfo {
|
||||||
|
vertex_shader = vert_shader,
|
||||||
|
fragment_shader = frag_shader,
|
||||||
|
primitive_type = .TRIANGLELIST,
|
||||||
|
target_info = sdl.GPUGraphicsPipelineTargetInfo {
|
||||||
|
color_target_descriptions = &sdl.GPUColorTargetDescription {
|
||||||
|
format = sdl.GetGPUSwapchainTextureFormat(device, window),
|
||||||
|
blend_state = sdl.GPUColorTargetBlendState {
|
||||||
|
src_color_blendfactor = .SRC_ALPHA,
|
||||||
|
dst_color_blendfactor = .ONE_MINUS_SRC_ALPHA,
|
||||||
|
color_blend_op = .ADD,
|
||||||
|
src_alpha_blendfactor = .ONE,
|
||||||
|
dst_alpha_blendfactor = .ONE_MINUS_SRC_ALPHA,
|
||||||
|
alpha_blend_op = .ADD,
|
||||||
|
color_write_mask = sdl.GPUColorComponentFlags{.R, .G, .B, .A},
|
||||||
|
enable_blend = true,
|
||||||
|
enable_color_write_mask = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
num_color_targets = 1,
|
||||||
|
},
|
||||||
|
vertex_input_state = sdl.GPUVertexInputState {
|
||||||
|
vertex_buffer_descriptions = &sdl.GPUVertexBufferDescription {
|
||||||
|
slot = 0,
|
||||||
|
input_rate = sdl.GPUVertexInputRate.INSTANCE,
|
||||||
|
instance_step_rate = 1,
|
||||||
|
pitch = size_of(Quad),
|
||||||
|
},
|
||||||
|
num_vertex_buffers = 1,
|
||||||
|
vertex_attributes = raw_data(vertex_attributes[:]),
|
||||||
|
num_vertex_attributes = 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl_pipeline := sdl.CreateGPUGraphicsPipeline(device, pipeline_info)
|
||||||
|
if sdl_pipeline == nil {
|
||||||
|
log.error("Failed to create quad graphics pipeline:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.ReleaseGPUShader(device, vert_shader)
|
||||||
|
sdl.ReleaseGPUShader(device, frag_shader)
|
||||||
|
|
||||||
|
// Create buffers
|
||||||
|
instance_buffer := create_buffer(
|
||||||
|
device,
|
||||||
|
size_of(Quad) * BUFFER_INIT_SIZE,
|
||||||
|
sdl.GPUBufferUsageFlags { .VERTEX },
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline := QuadPipeline{instance_buffer, BUFFER_INIT_SIZE, sdl_pipeline}
|
||||||
|
|
||||||
|
log.debug("Done creating quad pipeline")
|
||||||
|
return pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
upload_quads :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
|
||||||
|
num_quads := u32(len(tmp_quads))
|
||||||
|
size := num_quads * size_of(Quad)
|
||||||
|
|
||||||
|
resize_buffer(device, &quad_pipeline.instance_buffer, size, sdl.GPUBufferUsageFlags { .VERTEX })
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
i_array := sdl.MapGPUTransferBuffer(device, quad_pipeline.instance_buffer.transfer, false)
|
||||||
|
mem.copy(i_array, raw_data(tmp_quads), int(size))
|
||||||
|
sdl.UnmapGPUTransferBuffer(device, quad_pipeline.instance_buffer.transfer)
|
||||||
|
|
||||||
|
// Upload
|
||||||
|
sdl.UploadToGPUBuffer(
|
||||||
|
pass,
|
||||||
|
sdl.GPUTransferBufferLocation{transfer_buffer = quad_pipeline.instance_buffer.transfer},
|
||||||
|
sdl.GPUBufferRegion{buffer = quad_pipeline.instance_buffer.gpu, offset = 0, size = size},
|
||||||
|
false, // TODO figure out what cycling actually does
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
draw_quads :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
window: ^sdl.Window,
|
||||||
|
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||||
|
swapchain_texture: ^sdl.GPUTexture,
|
||||||
|
swapchain_w: u32,
|
||||||
|
swapchain_h: u32,
|
||||||
|
layer: ^Layer,
|
||||||
|
load_op: sdl.GPULoadOp,
|
||||||
|
) {
|
||||||
|
if layer.quad_len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render_pass := sdl.BeginGPURenderPass(
|
||||||
|
cmd_buffer,
|
||||||
|
&sdl.GPUColorTargetInfo {
|
||||||
|
texture = swapchain_texture,
|
||||||
|
clear_color = sdl.FColor{1.0, 1.0, 1.0, 1.0},
|
||||||
|
load_op = load_op,
|
||||||
|
store_op = sdl.GPUStoreOp.STORE,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
sdl.BindGPUGraphicsPipeline(render_pass, quad_pipeline.sdl_pipeline)
|
||||||
|
|
||||||
|
sdl.BindGPUVertexBuffers(
|
||||||
|
render_pass,
|
||||||
|
0,
|
||||||
|
&sdl.GPUBufferBinding{buffer = quad_pipeline.instance_buffer.gpu, offset = 0},
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
push_globals(cmd_buffer, f32(swapchain_w), f32(swapchain_h))
|
||||||
|
|
||||||
|
quad_offset := layer.quad_instance_start
|
||||||
|
|
||||||
|
for &scissor, index in layer.scissors {
|
||||||
|
if scissor.quad_len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if scissor.bounds.w == 0 || scissor.bounds.h == 0 {
|
||||||
|
sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)})
|
||||||
|
} else {
|
||||||
|
sdl.SetGPUScissor(render_pass, scissor.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.DrawGPUPrimitives(render_pass, 6, scissor.quad_len, 0, quad_offset)
|
||||||
|
quad_offset += scissor.quad_len
|
||||||
|
}
|
||||||
|
sdl.EndGPURenderPass(render_pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_quad_pipeline :: proc(device: ^sdl.GPUDevice) {
|
||||||
|
destroy_buffer(device, &quad_pipeline.instance_buffer)
|
||||||
|
sdl.ReleaseGPUGraphicsPipeline(device, quad_pipeline.sdl_pipeline)
|
||||||
|
}
|
307
renderer/renderer.odin
Normal file
307
renderer/renderer.odin
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
package renderer
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:c"
|
||||||
|
import "core:log"
|
||||||
|
import "core:os"
|
||||||
|
import "core:strings"
|
||||||
|
import clay "library:clay"
|
||||||
|
import sdl_ttf "library:sdl3_ttf"
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
when ODIN_OS == .Darwin {
|
||||||
|
SHADER_TYPE :: sdl.GPUShaderFormat{.MSL}
|
||||||
|
ENTRY_POINT :: "main0"
|
||||||
|
} else {
|
||||||
|
SHADER_TYPE :: sdl.GPUShaderFormat{.SPIRV}
|
||||||
|
ENTRY_POINT :: "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
BUFFER_INIT_SIZE: u32 : 256
|
||||||
|
|
||||||
|
dpi_scaling: f32 = 1.0
|
||||||
|
layers: [dynamic]Layer
|
||||||
|
quad_pipeline: QuadPipeline
|
||||||
|
text_pipeline: TextPipeline
|
||||||
|
odin_context: runtime.Context
|
||||||
|
|
||||||
|
// TODO New layer for each z-index/batch
|
||||||
|
Layer :: struct {
|
||||||
|
quad_instance_start: u32,
|
||||||
|
quad_len: u32,
|
||||||
|
text_instance_start: u32,
|
||||||
|
text_instance_len: u32,
|
||||||
|
text_vertex_start: u32,
|
||||||
|
text_vertex_len: u32,
|
||||||
|
text_index_start: u32,
|
||||||
|
text_index_len: u32,
|
||||||
|
scissors: [dynamic]Scissor,
|
||||||
|
}
|
||||||
|
|
||||||
|
Scissor :: struct {
|
||||||
|
bounds: sdl.Rect,
|
||||||
|
quad_start: u32,
|
||||||
|
quad_len: u32,
|
||||||
|
text_start: u32,
|
||||||
|
text_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the renderer.
|
||||||
|
init :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
window: ^sdl.Window,
|
||||||
|
window_width: f32,
|
||||||
|
window_height: f32,
|
||||||
|
ctx: runtime.Context,
|
||||||
|
) {
|
||||||
|
odin_context = ctx
|
||||||
|
dpi_scaling = sdl.GetWindowDisplayScale(window)
|
||||||
|
log.debug("Window DPI scaling:", dpi_scaling)
|
||||||
|
|
||||||
|
min_memory_size: c.size_t = cast(c.size_t)clay.MinMemorySize()
|
||||||
|
memory := make([^]u8, min_memory_size)
|
||||||
|
arena := clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory)
|
||||||
|
|
||||||
|
clay.Initialize(arena, {window_width, window_height}, {handler = clay_error_handler})
|
||||||
|
clay.SetMeasureTextFunction(measure_text, nil)
|
||||||
|
quad_pipeline = create_quad_pipeline(device, window)
|
||||||
|
text_pipeline = create_text_pipeline(device, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
|
||||||
|
context = odin_context
|
||||||
|
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
measure_text :: proc "c" (
|
||||||
|
text: clay.StringSlice,
|
||||||
|
config: ^clay.TextElementConfig,
|
||||||
|
user_data: rawptr,
|
||||||
|
) -> clay.Dimensions {
|
||||||
|
context = odin_context
|
||||||
|
text := string(text.chars[:text.length])
|
||||||
|
c_text := strings.clone_to_cstring(text, context.temp_allocator)
|
||||||
|
w, h: c.int
|
||||||
|
if !sdl_ttf.GetStringSize(get_font(config.fontId, config.fontSize), c_text, 0, &w, &h) {
|
||||||
|
log.error("Failed to measure text", sdl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return clay.Dimensions{width = f32(w) / dpi_scaling, height = f32(h) / dpi_scaling}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy :: proc(device: ^sdl.GPUDevice) {
|
||||||
|
destroy_quad_pipeline(device)
|
||||||
|
destroy_text_pipeline(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload data to the GPU
|
||||||
|
prepare :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
window: ^sdl.Window,
|
||||||
|
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||||
|
render_commands: ^clay.ClayArray(clay.RenderCommand),
|
||||||
|
mouse_delta: [2]f32,
|
||||||
|
frame_time: f32,
|
||||||
|
) {
|
||||||
|
mouse_x, mouse_y: f32
|
||||||
|
mouse_flags := sdl.GetMouseState(&mouse_x, &mouse_y)
|
||||||
|
// Currently MacOS blocks main thread when resizing, this will be fixed with next SDL3 release
|
||||||
|
window_w, window_h: c.int
|
||||||
|
window_size := sdl.GetWindowSize(window, &window_w, &window_h)
|
||||||
|
|
||||||
|
// Update clay internals
|
||||||
|
clay.SetPointerState(clay.Vector2{mouse_x, mouse_y}, .LEFT in mouse_flags)
|
||||||
|
clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_delta, frame_time)
|
||||||
|
clay.SetLayoutDimensions({f32(window_w), f32(window_h)})
|
||||||
|
|
||||||
|
clear(&layers)
|
||||||
|
clear(&tmp_quads)
|
||||||
|
clear(&tmp_text)
|
||||||
|
|
||||||
|
tmp_quads = make([dynamic]Quad, 0, quad_pipeline.num_instances, context.temp_allocator)
|
||||||
|
tmp_text = make([dynamic]Text, 0, 20, context.temp_allocator)
|
||||||
|
|
||||||
|
layer := Layer {
|
||||||
|
scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator),
|
||||||
|
}
|
||||||
|
scissor := Scissor{}
|
||||||
|
|
||||||
|
// Parse render commands
|
||||||
|
for i in 0 ..< int(render_commands.length) {
|
||||||
|
render_command := clay.RenderCommandArray_Get(render_commands, cast(i32)i)
|
||||||
|
bounds := render_command.boundingBox
|
||||||
|
|
||||||
|
switch (render_command.commandType) {
|
||||||
|
case clay.RenderCommandType.None:
|
||||||
|
case clay.RenderCommandType.Text:
|
||||||
|
render_data := render_command.renderData.text
|
||||||
|
text := string(render_data.stringContents.chars[:render_data.stringContents.length])
|
||||||
|
c_text := strings.clone_to_cstring(text, context.temp_allocator)
|
||||||
|
sdl_text := text_pipeline.cache[render_command.id]
|
||||||
|
|
||||||
|
if sdl_text == nil {
|
||||||
|
// Cache a SDL text object
|
||||||
|
sdl_text = sdl_ttf.CreateText(
|
||||||
|
text_pipeline.engine,
|
||||||
|
get_font(render_data.fontId, render_data.fontSize),
|
||||||
|
c_text,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
text_pipeline.cache[render_command.id] = sdl_text
|
||||||
|
} else {
|
||||||
|
// Update text with c_string
|
||||||
|
sdl_ttf.SetTextString(sdl_text, c_text, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := sdl_ttf.GetGPUTextDrawData(sdl_text)
|
||||||
|
|
||||||
|
if sdl_text == nil {
|
||||||
|
log.error("Could not create SDL text:", sdl.GetError())
|
||||||
|
} else {
|
||||||
|
append(
|
||||||
|
&tmp_text,
|
||||||
|
Text{sdl_text, {bounds.x, bounds.y}, f32_color(render_data.textColor)},
|
||||||
|
)
|
||||||
|
layer.text_instance_len += 1
|
||||||
|
layer.text_vertex_len += u32(data.num_verticies)
|
||||||
|
layer.text_index_len += u32(data.num_indices)
|
||||||
|
scissor.text_len += 1
|
||||||
|
}
|
||||||
|
case clay.RenderCommandType.Image:
|
||||||
|
case clay.RenderCommandType.ScissorStart:
|
||||||
|
bounds := sdl.Rect {
|
||||||
|
c.int(bounds.x * dpi_scaling),
|
||||||
|
c.int(bounds.y * dpi_scaling),
|
||||||
|
c.int(bounds.width * dpi_scaling),
|
||||||
|
c.int(bounds.height * dpi_scaling),
|
||||||
|
}
|
||||||
|
new := new_scissor(&scissor)
|
||||||
|
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
||||||
|
append(&layer.scissors, scissor)
|
||||||
|
}
|
||||||
|
scissor = new
|
||||||
|
scissor.bounds = bounds
|
||||||
|
case clay.RenderCommandType.ScissorEnd:
|
||||||
|
new := new_scissor(&scissor)
|
||||||
|
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
||||||
|
append(&layer.scissors, scissor)
|
||||||
|
}
|
||||||
|
scissor = new
|
||||||
|
case clay.RenderCommandType.Rectangle:
|
||||||
|
render_data := render_command.renderData.rectangle
|
||||||
|
color := f32_color(render_data.backgroundColor)
|
||||||
|
cr := render_data.cornerRadius
|
||||||
|
quad := Quad {
|
||||||
|
position_scale = {bounds.x, bounds.y, bounds.width, bounds.height},
|
||||||
|
corner_radii = {cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft},
|
||||||
|
color = color,
|
||||||
|
}
|
||||||
|
append(&tmp_quads, quad)
|
||||||
|
layer.quad_len += 1
|
||||||
|
scissor.quad_len += 1
|
||||||
|
case clay.RenderCommandType.Border:
|
||||||
|
render_data := render_command.renderData.border
|
||||||
|
cr := render_data.cornerRadius
|
||||||
|
quad := Quad {
|
||||||
|
position_scale = {bounds.x, bounds.y, bounds.width, bounds.height},
|
||||||
|
corner_radii = {cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft},
|
||||||
|
//TODO: I was using a hack here to get the underlying color of the quad in the layout and then pass it into the
|
||||||
|
// right border color, but Clay got rid of multi color support for borders so I need to just make a dedicated border pipeline
|
||||||
|
color = f32_color(
|
||||||
|
clay.Color{render_data.color.r, render_data.color.g, render_data.color.b, 0.0},
|
||||||
|
),
|
||||||
|
border_color = f32_color(render_data.color),
|
||||||
|
// We only support one border width at the moment
|
||||||
|
border_width = f32(render_data.width.top),
|
||||||
|
}
|
||||||
|
// Technically these should be drawn on top of everything else including children, but
|
||||||
|
// for our use case we can just chuck these in with the quad pipeline
|
||||||
|
append(&tmp_quads, quad)
|
||||||
|
layer.quad_len += 1
|
||||||
|
scissor.quad_len += 1
|
||||||
|
case clay.RenderCommandType.Custom:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO start new layers with z-index changes
|
||||||
|
append(&layer.scissors, scissor)
|
||||||
|
append(&layers, layer)
|
||||||
|
|
||||||
|
// Upload primitives to GPU
|
||||||
|
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||||
|
upload_quads(device, copy_pass)
|
||||||
|
upload_text(device, copy_pass)
|
||||||
|
sdl.EndGPUCopyPass(copy_pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render primitives
|
||||||
|
draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCommandBuffer) {
|
||||||
|
swapchain_texture: ^sdl.GPUTexture
|
||||||
|
w, h: u32
|
||||||
|
if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &w, &h) {
|
||||||
|
log.error("Failed to acquire swapchain texture:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if swapchain_texture == nil {
|
||||||
|
log.error("Failed to acquire swapchain texture:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for &layer, index in layers {
|
||||||
|
draw_quads(
|
||||||
|
device,
|
||||||
|
window,
|
||||||
|
cmd_buffer,
|
||||||
|
swapchain_texture,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
&layer,
|
||||||
|
index == 0 ? sdl.GPULoadOp.CLEAR : sdl.GPULoadOp.LOAD,
|
||||||
|
)
|
||||||
|
draw_text(device, window, cmd_buffer, swapchain_texture, w, h, &layer)
|
||||||
|
//TODO draw other primitives in layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ortho_rh :: proc(
|
||||||
|
left: f32,
|
||||||
|
right: f32,
|
||||||
|
bottom: f32,
|
||||||
|
top: f32,
|
||||||
|
near: f32,
|
||||||
|
far: f32,
|
||||||
|
) -> matrix[4, 4]f32 {
|
||||||
|
return matrix[4, 4]f32{
|
||||||
|
2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left),
|
||||||
|
0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom),
|
||||||
|
0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near),
|
||||||
|
0.0, 0.0, 0.0, 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f32_color :: proc(color: clay.Color) -> [4]f32 {
|
||||||
|
return [4]f32{color.x / 255.0, color.y / 255.0, color.z / 255.0, color.w / 255.0}
|
||||||
|
}
|
||||||
|
|
||||||
|
Globals :: struct {
|
||||||
|
projection: matrix[4, 4]f32,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
push_globals :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, w: f32, h: f32) {
|
||||||
|
globals := Globals {
|
||||||
|
ortho_rh(left = 0.0, top = 0.0, right = f32(w), bottom = f32(h), near = -1.0, far = 1.0),
|
||||||
|
dpi_scaling,
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.PushGPUVertexUniformData(cmd_buffer, 0, &globals, size_of(Globals))
|
||||||
|
}
|
||||||
|
|
||||||
|
new_scissor :: proc(old: ^Scissor) -> Scissor {
|
||||||
|
return Scissor {
|
||||||
|
quad_start = old.quad_start + old.quad_len,
|
||||||
|
text_start = old.text_start + old.text_len,
|
||||||
|
}
|
||||||
|
}
|
BIN
renderer/res/fonts/JetBrainsMono-Bold.ttf
Normal file
BIN
renderer/res/fonts/JetBrainsMono-Bold.ttf
Normal file
Binary file not shown.
BIN
renderer/res/fonts/JetBrainsMono-Regular.ttf
Normal file
BIN
renderer/res/fonts/JetBrainsMono-Regular.ttf
Normal file
Binary file not shown.
33
renderer/res/shaders/raw/quad.frag
Normal file
33
renderer/res/shaders/raw/quad.frag
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 color;
|
||||||
|
layout(location = 1) in vec4 corners;
|
||||||
|
layout(location = 2) in vec4 center_scale;
|
||||||
|
layout(location = 3) in vec4 border_color;
|
||||||
|
layout(location = 4) in float border_width;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
const float AA_THRESHOLD = 1.0;
|
||||||
|
|
||||||
|
float rounded_box(vec2 p, vec2 b, in vec4 r) {
|
||||||
|
r.xy = (p.x > 0.0) ? r.xy : r.zw;
|
||||||
|
r.x = (p.y > 0.0) ? r.x : r.y;
|
||||||
|
vec2 q = abs(p) - b + r.x;
|
||||||
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (corners == vec4(0.0) && border_width == 0.0) {
|
||||||
|
out_color = color;
|
||||||
|
} else {
|
||||||
|
float d = rounded_box(gl_FragCoord.xy - center_scale.xy, center_scale.zw, corners);
|
||||||
|
if (d > AA_THRESHOLD) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
float alpha = 1.0 - smoothstep(-AA_THRESHOLD, AA_THRESHOLD, d);
|
||||||
|
vec4 border_mixed = mix(color, border_color, 1.0 - smoothstep(0.0, AA_THRESHOLD * 2.0, abs(d) - border_width - AA_THRESHOLD));
|
||||||
|
|
||||||
|
out_color = vec4(border_mixed.rgb, border_mixed.a * alpha);
|
||||||
|
}
|
||||||
|
}
|
54
renderer/res/shaders/raw/quad.vert
Normal file
54
renderer/res/shaders/raw/quad.vert
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 v_pos_scale;
|
||||||
|
layout(location = 1) in vec4 v_corners;
|
||||||
|
layout(location = 2) in vec4 v_color;
|
||||||
|
layout(location = 3) in vec4 v_border_color;
|
||||||
|
layout(location = 4) in float v_border_width;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color;
|
||||||
|
layout(location = 1) out vec4 corners;
|
||||||
|
layout(location = 2) out vec4 center_scale;
|
||||||
|
layout(location = 3) out vec4 border_color;
|
||||||
|
layout(location = 4) out float border_width;
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform Uniforms {
|
||||||
|
mat4 projection;
|
||||||
|
float dpi_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
const vec2 positions[6] = vec2[](
|
||||||
|
vec2(1.0, 1.0), // top left
|
||||||
|
vec2(1.0, 0.0), // top right
|
||||||
|
vec2(0.0, 0.0), // bottom right
|
||||||
|
vec2(0.0, 0.0), // bottom right
|
||||||
|
vec2(0.0, 1.0), // bottom left
|
||||||
|
vec2(1.0, 1.0) // top left
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float min_corner_radius = min(v_pos_scale.z, v_pos_scale.w) * 0.5;
|
||||||
|
vec4 corner_radii = vec4(
|
||||||
|
min(v_corners.x, min_corner_radius),
|
||||||
|
min(v_corners.y, min_corner_radius),
|
||||||
|
min(v_corners.z, min_corner_radius),
|
||||||
|
min(v_corners.w, min_corner_radius)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract position and scale from position_scale
|
||||||
|
vec2 position = v_pos_scale.xy * dpi_scale;
|
||||||
|
vec2 scale = v_pos_scale.zw * dpi_scale;
|
||||||
|
|
||||||
|
vec2 local_pos = positions[gl_VertexIndex];
|
||||||
|
local_pos *= scale;
|
||||||
|
local_pos += position;
|
||||||
|
|
||||||
|
// Pass values to fragment shader
|
||||||
|
color = v_color;
|
||||||
|
corners = corner_radii * dpi_scale;
|
||||||
|
center_scale = vec4(position + scale * 0.5, v_pos_scale.zw);
|
||||||
|
border_color = v_border_color;
|
||||||
|
border_width = v_border_width * dpi_scale;
|
||||||
|
|
||||||
|
gl_Position = projection * vec4(local_pos, 0.0, 1.0);
|
||||||
|
}
|
10
renderer/res/shaders/raw/scene.frag
Normal file
10
renderer/res/shaders/raw/scene.frag
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 color;
|
||||||
|
layout(location = 1) in vec4 roughness_metallic_uv;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = color;
|
||||||
|
}
|
28
renderer/res/shaders/raw/scene.vert
Normal file
28
renderer/res/shaders/raw/scene.vert
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 v_pos;
|
||||||
|
layout (location = 1) in vec4 v_normal;
|
||||||
|
layout (location = 2) in vec2 v_uv;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color;
|
||||||
|
layout(location = 1) out vec4 roughness_metallic_uv;
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
vec4 base_color;
|
||||||
|
float roughness;
|
||||||
|
float metallic;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBlock {
|
||||||
|
mat4 projection;
|
||||||
|
float dpi_scale;
|
||||||
|
Material material;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 local_pos = v_pos.xyz;
|
||||||
|
gl_Position = projection * vec4(local_pos, 1.0);
|
||||||
|
|
||||||
|
color = material.base_color;
|
||||||
|
roughness_metallic_uv = vec4(material.roughness, material.metallic, v_uv);
|
||||||
|
}
|
12
renderer/res/shaders/raw/text.frag
Normal file
12
renderer/res/shaders/raw/text.frag
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 color;
|
||||||
|
layout (location = 1) in vec2 uv;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout (set = 2, binding = 0) uniform sampler2D atlas;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = color * texture(atlas, uv);
|
||||||
|
}
|
23
renderer/res/shaders/raw/text.vert
Normal file
23
renderer/res/shaders/raw/text.vert
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 v_pos_uv;
|
||||||
|
layout (location = 1) in vec4 v_color;
|
||||||
|
layout (location = 2) in vec2 text_pos;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
layout (location = 1) out vec2 uv;
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform Uniforms {
|
||||||
|
mat4 projection;
|
||||||
|
float dpi_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = v_color;
|
||||||
|
uv = v_pos_uv.zw;
|
||||||
|
|
||||||
|
vec2 local_pos = v_pos_uv.xy;
|
||||||
|
local_pos += text_pos * dpi_scale;
|
||||||
|
|
||||||
|
gl_Position = projection * vec4(local_pos, 0.0, 1.0);
|
||||||
|
}
|
440
renderer/text.odin
Normal file
440
renderer/text.odin
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
package renderer
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
import sdl_ttf "library:sdl3_ttf"
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
JETBRAINS_MONO_REGULAR: u16 : 0
|
||||||
|
JETBRAINS_MONO_BOLD: u16 : 1
|
||||||
|
NUM_FONTS :: 2
|
||||||
|
MAX_FONT_SIZE :: 120
|
||||||
|
|
||||||
|
tmp_text: [dynamic]Text
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
jetbrains_mono_regular := #load("res/fonts/JetBrainsMono-Regular.ttf")
|
||||||
|
@(private = "file")
|
||||||
|
jetbrains_mono_bold := #load("res/fonts/JetBrainsMono-Bold.ttf")
|
||||||
|
|
||||||
|
TextPipeline :: struct {
|
||||||
|
engine: ^sdl_ttf.TextEngine,
|
||||||
|
fonts: [NUM_FONTS][MAX_FONT_SIZE]^sdl_ttf.Font,
|
||||||
|
sdl_pipeline: ^sdl.GPUGraphicsPipeline,
|
||||||
|
vertex_buffer: Buffer,
|
||||||
|
index_buffer: Buffer,
|
||||||
|
instance_buffer: Buffer,
|
||||||
|
sampler: ^sdl.GPUSampler,
|
||||||
|
cache: map[u32]^sdl_ttf.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
get_font :: proc(id: u16, size: u16) -> ^sdl_ttf.Font {
|
||||||
|
font := text_pipeline.fonts[id > 1 ? 0 : id][size > 0 ? size : 16]
|
||||||
|
|
||||||
|
if font == nil {
|
||||||
|
log.debug("Font not found for size", size, "+ adding")
|
||||||
|
jb_mono_reg_rwops := sdl.IOFromConstMem(
|
||||||
|
raw_data(jetbrains_mono_regular[:]),
|
||||||
|
len(jetbrains_mono_regular),
|
||||||
|
)
|
||||||
|
f := sdl_ttf.OpenFontIO(jb_mono_reg_rwops, true, f32(size))
|
||||||
|
if f == nil {
|
||||||
|
log.error("Failed to font with size:", size, sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
font = f
|
||||||
|
sdl_ttf.SetFontSizeDPI(f, f32(size), 72 * i32(dpi_scaling), 72 * i32(dpi_scaling))
|
||||||
|
text_pipeline.fonts[id][size] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
return font
|
||||||
|
}
|
||||||
|
|
||||||
|
Text :: struct {
|
||||||
|
ref: ^sdl_ttf.Text,
|
||||||
|
position: [2]f32,
|
||||||
|
color: [4]f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For upload
|
||||||
|
TextVert :: struct {
|
||||||
|
pos_uv: [4]f32,
|
||||||
|
color: [4]f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
create_text_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> TextPipeline {
|
||||||
|
log.debug("Creating text pipeline")
|
||||||
|
if !sdl_ttf.Init() {
|
||||||
|
log.error("Failed to initialize TTF", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
when ODIN_OS == .Darwin {
|
||||||
|
vert_raw := #load("res/shaders/compiled/text.vert.metal")
|
||||||
|
frag_raw := #load("res/shaders/compiled/text.frag.metal")
|
||||||
|
} else {
|
||||||
|
vert_raw := #load("res/shaders/compiled/text.vert.spv")
|
||||||
|
frag_raw := #load("res/shaders/compiled/text.frag.spv")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Loaded", len(vert_raw), "vert bytes")
|
||||||
|
log.debug("Loaded", len(frag_raw), "frag bytes")
|
||||||
|
|
||||||
|
vert_info := sdl.GPUShaderCreateInfo {
|
||||||
|
code_size = len(vert_raw),
|
||||||
|
code = raw_data(vert_raw),
|
||||||
|
entrypoint = ENTRY_POINT,
|
||||||
|
format = SHADER_TYPE,
|
||||||
|
stage = sdl.GPUShaderStage.VERTEX,
|
||||||
|
num_uniform_buffers = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
frag_info := sdl.GPUShaderCreateInfo {
|
||||||
|
code_size = len(frag_raw),
|
||||||
|
code = raw_data(frag_raw),
|
||||||
|
entrypoint = ENTRY_POINT,
|
||||||
|
format = SHADER_TYPE,
|
||||||
|
stage = sdl.GPUShaderStage.FRAGMENT,
|
||||||
|
num_samplers = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
vert_shader := sdl.CreateGPUShader(device, vert_info)
|
||||||
|
if vert_shader == nil {
|
||||||
|
log.error("Could not create vertex shader:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
frag_shader := sdl.CreateGPUShader(device, frag_info)
|
||||||
|
if frag_shader == nil {
|
||||||
|
log.error("Could not create fragment shader:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_attributes: [3]sdl.GPUVertexAttribute = {
|
||||||
|
// vertex position & uv
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 0,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = 0,
|
||||||
|
},
|
||||||
|
// color
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 0,
|
||||||
|
location = 1,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT4,
|
||||||
|
offset = size_of(f32) * 4,
|
||||||
|
},
|
||||||
|
// Instance position data
|
||||||
|
sdl.GPUVertexAttribute {
|
||||||
|
buffer_slot = 1,
|
||||||
|
location = 2,
|
||||||
|
format = sdl.GPUVertexElementFormat.FLOAT2,
|
||||||
|
offset = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_descriptions: [2]sdl.GPUVertexBufferDescription = {
|
||||||
|
sdl.GPUVertexBufferDescription{slot = 0, input_rate = .VERTEX, pitch = size_of(TextVert)},
|
||||||
|
sdl.GPUVertexBufferDescription {
|
||||||
|
slot = 1,
|
||||||
|
input_rate = .INSTANCE,
|
||||||
|
pitch = size_of([2]f32),
|
||||||
|
instance_step_rate = 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler_info := sdl.GPUSamplerCreateInfo {
|
||||||
|
min_filter = .LINEAR,
|
||||||
|
mag_filter = .LINEAR,
|
||||||
|
mipmap_mode = .LINEAR,
|
||||||
|
address_mode_u = .CLAMP_TO_EDGE,
|
||||||
|
address_mode_v = .CLAMP_TO_EDGE,
|
||||||
|
address_mode_w = .CLAMP_TO_EDGE,
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler := sdl.CreateGPUSampler(device, sampler_info)
|
||||||
|
if sampler == nil {
|
||||||
|
log.error("Could not create GPU sampler:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline_info := sdl.GPUGraphicsPipelineCreateInfo {
|
||||||
|
vertex_shader = vert_shader,
|
||||||
|
fragment_shader = frag_shader,
|
||||||
|
primitive_type = .TRIANGLELIST,
|
||||||
|
target_info = sdl.GPUGraphicsPipelineTargetInfo {
|
||||||
|
color_target_descriptions = &sdl.GPUColorTargetDescription {
|
||||||
|
format = sdl.GetGPUSwapchainTextureFormat(device, window),
|
||||||
|
blend_state = sdl.GPUColorTargetBlendState {
|
||||||
|
enable_blend = true,
|
||||||
|
color_write_mask = sdl.GPUColorComponentFlags{.R, .G, .B, .A},
|
||||||
|
alpha_blend_op = sdl.GPUBlendOp.ADD,
|
||||||
|
src_alpha_blendfactor = sdl.GPUBlendFactor.SRC_ALPHA,
|
||||||
|
dst_alpha_blendfactor = sdl.GPUBlendFactor.ONE_MINUS_SRC_ALPHA,
|
||||||
|
color_blend_op = sdl.GPUBlendOp.ADD,
|
||||||
|
src_color_blendfactor = sdl.GPUBlendFactor.SRC_ALPHA,
|
||||||
|
dst_color_blendfactor = sdl.GPUBlendFactor.ONE_MINUS_SRC_ALPHA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
num_color_targets = 1,
|
||||||
|
},
|
||||||
|
vertex_input_state = sdl.GPUVertexInputState {
|
||||||
|
vertex_buffer_descriptions = raw_data(buffer_descriptions[:]),
|
||||||
|
num_vertex_buffers = 2,
|
||||||
|
vertex_attributes = raw_data(vertex_attributes[:]),
|
||||||
|
num_vertex_attributes = 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl_pipeline := sdl.CreateGPUGraphicsPipeline(device, pipeline_info)
|
||||||
|
if sdl_pipeline == nil {
|
||||||
|
log.error("Failed to create quad graphics pipeline:", sdl.GetError())
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.ReleaseGPUShader(device, vert_shader)
|
||||||
|
sdl.ReleaseGPUShader(device, frag_shader)
|
||||||
|
|
||||||
|
// Create engine
|
||||||
|
engine := sdl_ttf.CreateGPUTextEngine(device)
|
||||||
|
if engine == nil {
|
||||||
|
log.error("Could not create text engine")
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTERCLOCKWISE)
|
||||||
|
|
||||||
|
// Create buffers
|
||||||
|
vertex_buffer := create_buffer(
|
||||||
|
device,
|
||||||
|
size_of(TextVert) * BUFFER_INIT_SIZE,
|
||||||
|
sdl.GPUBufferUsageFlags{.VERTEX},
|
||||||
|
)
|
||||||
|
index_buffer := create_buffer(
|
||||||
|
device,
|
||||||
|
size_of(c.int) * BUFFER_INIT_SIZE,
|
||||||
|
sdl.GPUBufferUsageFlags{.INDEX},
|
||||||
|
)
|
||||||
|
instance_buffer := create_buffer(
|
||||||
|
device,
|
||||||
|
size_of([2]f32) * BUFFER_INIT_SIZE,
|
||||||
|
sdl.GPUBufferUsageFlags{.VERTEX},
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline := TextPipeline {
|
||||||
|
engine,
|
||||||
|
[NUM_FONTS][MAX_FONT_SIZE]^sdl_ttf.Font{},
|
||||||
|
sdl_pipeline,
|
||||||
|
vertex_buffer,
|
||||||
|
index_buffer,
|
||||||
|
instance_buffer,
|
||||||
|
sampler,
|
||||||
|
make(map[u32]^sdl_ttf.Text),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Done creating text pipeline")
|
||||||
|
return pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
upload_text :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
|
||||||
|
vertices := make([dynamic]TextVert, 0, BUFFER_INIT_SIZE, context.temp_allocator)
|
||||||
|
indices := make([dynamic]c.int, 0, BUFFER_INIT_SIZE, context.temp_allocator)
|
||||||
|
instances := make([dynamic][2]f32, 0, BUFFER_INIT_SIZE, context.temp_allocator)
|
||||||
|
|
||||||
|
for &text, index in tmp_text {
|
||||||
|
append(&instances, text.position)
|
||||||
|
data := sdl_ttf.GetGPUTextDrawData(text.ref)
|
||||||
|
|
||||||
|
for data != nil {
|
||||||
|
for i in 0 ..< data.num_verticies {
|
||||||
|
pos := data.vertex_positions[i]
|
||||||
|
uv := data.uvs[i]
|
||||||
|
color := text.color
|
||||||
|
append(&vertices, TextVert{{pos.x, -pos.y, uv.x, uv.y}, color})
|
||||||
|
}
|
||||||
|
append(&indices, ..data.indices[:data.num_indices])
|
||||||
|
data = data.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize buffers if needed
|
||||||
|
vertices_size := u32(len(vertices) * size_of(TextVert))
|
||||||
|
indices_size := u32(len(indices) * size_of(c.int))
|
||||||
|
instances_size := u32(len(instances) * size_of([2]f32))
|
||||||
|
|
||||||
|
resize_buffer(
|
||||||
|
device,
|
||||||
|
&text_pipeline.vertex_buffer,
|
||||||
|
vertices_size,
|
||||||
|
sdl.GPUBufferUsageFlags{.VERTEX},
|
||||||
|
)
|
||||||
|
resize_buffer(
|
||||||
|
device,
|
||||||
|
&text_pipeline.index_buffer,
|
||||||
|
indices_size,
|
||||||
|
sdl.GPUBufferUsageFlags{.INDEX},
|
||||||
|
)
|
||||||
|
resize_buffer(
|
||||||
|
device,
|
||||||
|
&text_pipeline.instance_buffer,
|
||||||
|
instances_size,
|
||||||
|
sdl.GPUBufferUsageFlags{.VERTEX},
|
||||||
|
)
|
||||||
|
|
||||||
|
vertex_array := sdl.MapGPUTransferBuffer(device, text_pipeline.vertex_buffer.transfer, true)
|
||||||
|
mem.copy(vertex_array, raw_data(vertices), int(vertices_size))
|
||||||
|
sdl.UnmapGPUTransferBuffer(device, text_pipeline.vertex_buffer.transfer)
|
||||||
|
|
||||||
|
index_array := sdl.MapGPUTransferBuffer(device, text_pipeline.index_buffer.transfer, true)
|
||||||
|
mem.copy(index_array, raw_data(indices), int(indices_size))
|
||||||
|
sdl.UnmapGPUTransferBuffer(device, text_pipeline.index_buffer.transfer)
|
||||||
|
|
||||||
|
instance_array := sdl.MapGPUTransferBuffer(
|
||||||
|
device,
|
||||||
|
text_pipeline.instance_buffer.transfer,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
mem.copy(instance_array, raw_data(instances), int(instances_size))
|
||||||
|
sdl.UnmapGPUTransferBuffer(device, text_pipeline.instance_buffer.transfer)
|
||||||
|
|
||||||
|
sdl.UploadToGPUBuffer(
|
||||||
|
pass,
|
||||||
|
sdl.GPUTransferBufferLocation{transfer_buffer = text_pipeline.vertex_buffer.transfer},
|
||||||
|
sdl.GPUBufferRegion {
|
||||||
|
buffer = text_pipeline.vertex_buffer.gpu,
|
||||||
|
offset = 0,
|
||||||
|
size = vertices_size,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
sdl.UploadToGPUBuffer(
|
||||||
|
pass,
|
||||||
|
sdl.GPUTransferBufferLocation{transfer_buffer = text_pipeline.index_buffer.transfer},
|
||||||
|
sdl.GPUBufferRegion {
|
||||||
|
buffer = text_pipeline.index_buffer.gpu,
|
||||||
|
offset = 0,
|
||||||
|
size = indices_size,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
|
sdl.UploadToGPUBuffer(
|
||||||
|
pass,
|
||||||
|
sdl.GPUTransferBufferLocation{transfer_buffer = text_pipeline.instance_buffer.transfer},
|
||||||
|
sdl.GPUBufferRegion {
|
||||||
|
buffer = text_pipeline.instance_buffer.gpu,
|
||||||
|
offset = 0,
|
||||||
|
size = instances_size,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
draw_text :: proc(
|
||||||
|
device: ^sdl.GPUDevice,
|
||||||
|
window: ^sdl.Window,
|
||||||
|
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||||
|
swapchain_texture: ^sdl.GPUTexture,
|
||||||
|
swapchain_w: u32,
|
||||||
|
swapchain_h: u32,
|
||||||
|
layer: ^Layer,
|
||||||
|
) {
|
||||||
|
if layer.text_instance_len == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render_pass := sdl.BeginGPURenderPass(
|
||||||
|
cmd_buffer,
|
||||||
|
&sdl.GPUColorTargetInfo {
|
||||||
|
texture = swapchain_texture,
|
||||||
|
load_op = sdl.GPULoadOp.LOAD,
|
||||||
|
store_op = sdl.GPUStoreOp.STORE,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
sdl.BindGPUGraphicsPipeline(render_pass, text_pipeline.sdl_pipeline)
|
||||||
|
|
||||||
|
v_bindings: [2]sdl.GPUBufferBinding = {
|
||||||
|
sdl.GPUBufferBinding{buffer = text_pipeline.vertex_buffer.gpu, offset = 0},
|
||||||
|
sdl.GPUBufferBinding{buffer = text_pipeline.instance_buffer.gpu, offset = 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.BindGPUVertexBuffers(render_pass, 0, raw_data(v_bindings[:]), 2)
|
||||||
|
sdl.BindGPUIndexBuffer(
|
||||||
|
render_pass,
|
||||||
|
sdl.GPUBufferBinding{buffer = text_pipeline.index_buffer.gpu, offset = 0},
|
||||||
|
._32BIT,
|
||||||
|
)
|
||||||
|
|
||||||
|
push_globals(cmd_buffer, f32(swapchain_w), f32(swapchain_h))
|
||||||
|
|
||||||
|
atlas: ^sdl.GPUTexture
|
||||||
|
|
||||||
|
layer_text := tmp_text[layer.text_instance_start:layer.text_instance_start +
|
||||||
|
layer.text_instance_len]
|
||||||
|
index_offset: u32 = layer.text_instance_start
|
||||||
|
vertex_offset: i32 = i32(layer.text_vertex_start)
|
||||||
|
instance_offset: u32 = layer.text_instance_start
|
||||||
|
|
||||||
|
for &scissor, index in layer.scissors {
|
||||||
|
if scissor.text_len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if scissor.bounds.w == 0 || scissor.bounds.h == 0 {
|
||||||
|
sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)})
|
||||||
|
} else {
|
||||||
|
sdl.SetGPUScissor(render_pass, scissor.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
for &text in layer_text[scissor.text_start:scissor.text_start + scissor.text_len] {
|
||||||
|
data := sdl_ttf.GetGPUTextDrawData(text.ref)
|
||||||
|
|
||||||
|
for data != nil {
|
||||||
|
if data.atlas_texture != atlas {
|
||||||
|
sdl.BindGPUFragmentSamplers(
|
||||||
|
render_pass,
|
||||||
|
0,
|
||||||
|
&sdl.GPUTextureSamplerBinding {
|
||||||
|
texture = data.atlas_texture,
|
||||||
|
sampler = text_pipeline.sampler,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
atlas = data.atlas_texture
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.DrawGPUIndexedPrimitives(
|
||||||
|
render_pass,
|
||||||
|
u32(data.num_indices),
|
||||||
|
1,
|
||||||
|
index_offset,
|
||||||
|
vertex_offset,
|
||||||
|
instance_offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
index_offset += u32(data.num_indices)
|
||||||
|
vertex_offset += data.num_verticies
|
||||||
|
|
||||||
|
data = data.next
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_offset += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl.EndGPURenderPass(render_pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_text_pipeline :: proc(device: ^sdl.GPUDevice) {
|
||||||
|
destroy_buffer(device, &text_pipeline.vertex_buffer)
|
||||||
|
destroy_buffer(device, &text_pipeline.index_buffer)
|
||||||
|
sdl.ReleaseGPUGraphicsPipeline(device, text_pipeline.sdl_pipeline)
|
||||||
|
}
|
Reference in New Issue
Block a user