Remove d3d12 renderer and replace with vulkan
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,6 +37,7 @@ nob.ilk
|
||||
# Generated source
|
||||
*.gen.h
|
||||
*.gen.cpp
|
||||
*.spv.h
|
||||
|
||||
# Misc
|
||||
*.exe
|
||||
|
||||
@@ -6,7 +6,7 @@ Graphical interface for automatically recording samples from existing analog aud
|
||||
|
||||
### Windows
|
||||
|
||||
Requires MSVC (Visual Studio 2019 Build Tools or later) with the Windows SDK and C++20 support.
|
||||
Requires MSVC (Visual Studio 2019 Build Tools or later) with the Windows SDK and C++20 support, and the [Vulkan SDK](https://vulkan.lunarg.com/) (for headers, `vulkan-1.lib`, and `glslc`). The build system looks for `%VULKAN_SDK%` or falls back to `C:\VulkanSDK\1.4.341.1`.
|
||||
|
||||
Open a Developer Command Prompt, then:
|
||||
|
||||
@@ -74,7 +74,7 @@ Interactive elements use subtle visual effects for a DAW-style look:
|
||||
|
||||
Custom SDF-based pipeline for UI rendering. Processes Clay's `Clay_RenderCommandArray` output directly — no intermediate scene graph.
|
||||
|
||||
- **Windows**: `renderer_dx12.cpp` — DirectX 12, HLSL shaders
|
||||
- **Windows**: `renderer_vulkan.cpp` — Vulkan, GLSL shaders (compiled to SPIR-V at build time)
|
||||
- **macOS**: `renderer_metal.mm` — Metal, MSL shaders
|
||||
|
||||
Both renderers share the same vertex format (18 floats), SDF rounded-rect shader, and Clay command processing logic. Font rasterization and text measurement use FreeType on both platforms with the embedded [Inter](https://rsms.me/inter/) typeface. The font atlas is a 1024x1024 R8 texture containing ASCII glyphs 32–126 rasterized at a fixed pixel size; text at other sizes is scaled from the atlas. The Inter TTF is embedded at build time as a C byte array (`font_inter.gen.h`) so there are no runtime font file dependencies. Font scale is multiplied by the platform DPI scale factor for crisp rendering on high-DPI displays.
|
||||
|
||||
95
build.c
95
build.c
@@ -445,10 +445,14 @@ int main(int argc, char **argv) {
|
||||
////////////////////////////////
|
||||
// Windows build (MSVC cl.exe)
|
||||
|
||||
// Vulkan SDK path — detected from environment or default install location
|
||||
static const char *get_vulkan_sdk_path(void) {
|
||||
const char *env = getenv("VULKAN_SDK");
|
||||
if (env && env[0]) return env;
|
||||
return "C:\\VulkanSDK\\1.4.341.1";
|
||||
}
|
||||
|
||||
static const char *link_libs[] = {
|
||||
"d3d12.lib",
|
||||
"dxgi.lib",
|
||||
"d3dcompiler.lib",
|
||||
"user32.lib",
|
||||
"gdi32.lib",
|
||||
"shell32.lib",
|
||||
@@ -458,6 +462,78 @@ static const char *link_libs[] = {
|
||||
"winmm.lib",
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header
|
||||
|
||||
static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, glslc_path, nob_temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
|
||||
Nob_Cmd_Opt opt = {0};
|
||||
return nob_cmd_run_opt(&cmd, opt);
|
||||
}
|
||||
|
||||
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
|
||||
Nob_String_Builder sb = {0};
|
||||
if (!nob_read_entire_file(spv_path, &sb)) return false;
|
||||
|
||||
FILE *out = fopen(header_path, "wb");
|
||||
if (!out) {
|
||||
nob_log(NOB_ERROR, "Could not open %s for writing", header_path);
|
||||
nob_sb_free(sb);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(out, "// Auto-generated from %s — do not edit\n", spv_path);
|
||||
fprintf(out, "#pragma once\n\n");
|
||||
// SPIR-V is U32-aligned, emit as uint32_t array
|
||||
size_t word_count = sb.count / 4;
|
||||
fprintf(out, "#include <stdint.h>\n\n");
|
||||
fprintf(out, "static const uint32_t %s[] = {\n", array_name);
|
||||
const uint32_t *words = (const uint32_t *)sb.items;
|
||||
for (size_t i = 0; i < word_count; i++) {
|
||||
if (i % 8 == 0) fprintf(out, " ");
|
||||
fprintf(out, "0x%08x,", words[i]);
|
||||
if (i % 8 == 7 || i == word_count - 1) fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, "};\n");
|
||||
|
||||
fclose(out);
|
||||
nob_sb_free(sb);
|
||||
nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool compile_and_embed_shaders(const char *build_dir) {
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
const char *glslc = nob_temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk);
|
||||
|
||||
const char *vert_src = "src/renderer/ui.v.glsl";
|
||||
const char *frag_src = "src/renderer/ui.f.glsl";
|
||||
const char *vert_spv = nob_temp_sprintf("%s/ui_vert.spv", build_dir);
|
||||
const char *frag_spv = nob_temp_sprintf("%s/ui_frag.spv", build_dir);
|
||||
const char *vert_hdr = "src/renderer/ui_vert.spv.h";
|
||||
const char *frag_hdr = "src/renderer/ui_frag.spv.h";
|
||||
|
||||
// Check if rebuild needed
|
||||
if (nob_needs_rebuild1(vert_hdr, vert_src)) {
|
||||
nob_log(NOB_INFO, "Compiling vertex shader");
|
||||
if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false;
|
||||
if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false;
|
||||
} else {
|
||||
nob_log(NOB_INFO, "Vertex shader is up to date");
|
||||
}
|
||||
|
||||
if (nob_needs_rebuild1(frag_hdr, frag_src)) {
|
||||
nob_log(NOB_INFO, "Compiling fragment shader");
|
||||
if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false;
|
||||
if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false;
|
||||
} else {
|
||||
nob_log(NOB_INFO, "Fragment shader is up to date");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
|
||||
const char *obj_dir = nob_temp_sprintf("%s\\lunasvg_obj", build_dir);
|
||||
const char *lib_path = debug ? "vendor\\lunasvg\\lunasvg_d.lib" : "vendor\\lunasvg\\lunasvg.lib";
|
||||
@@ -626,6 +702,8 @@ int main(int argc, char **argv) {
|
||||
remove("vendor\\freetype\\freetype.lib");
|
||||
remove("vendor\\freetype\\freetype_d.lib");
|
||||
remove("src\\renderer\\font_inter.gen.h");
|
||||
remove("src\\renderer\\ui_vert.spv.h");
|
||||
remove("src\\renderer\\ui_frag.spv.h");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -639,7 +717,14 @@ int main(int argc, char **argv) {
|
||||
if (!embed_font_file("assets/fonts/Inter-Regular.ttf",
|
||||
"src\\renderer\\font_inter.gen.h")) return 1;
|
||||
|
||||
// Unity build: single cl.exe invocation compiles main.cpp (which #includes everything)
|
||||
// Compile GLSL shaders to SPIR-V and embed as C headers
|
||||
if (!compile_and_embed_shaders(build_dir)) return 1;
|
||||
|
||||
const char *vk_sdk = get_vulkan_sdk_path();
|
||||
const char *vk_include = nob_temp_sprintf("/I%s/Include", vk_sdk);
|
||||
const char *vk_lib = nob_temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
|
||||
|
||||
// Unity build: single cl.exe invocation compiles main.cpp (which #includes everything including the vulkan renderer)
|
||||
{
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, "cl.exe");
|
||||
@@ -647,6 +732,7 @@ int main(int argc, char **argv) {
|
||||
nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
|
||||
nob_cmd_append(&cmd, "/Ivendor/lunasvg/include");
|
||||
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
|
||||
nob_cmd_append(&cmd, vk_include);
|
||||
nob_cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC");
|
||||
|
||||
if (debug) {
|
||||
@@ -668,6 +754,7 @@ int main(int argc, char **argv) {
|
||||
nob_cmd_append(&cmd, "/DEBUG");
|
||||
nob_cmd_append(&cmd, debug ? "vendor/lunasvg/lunasvg_d.lib" : "vendor/lunasvg/lunasvg.lib");
|
||||
nob_cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
|
||||
nob_cmd_append(&cmd, vk_lib);
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "audio/audio_coreaudio.cpp"
|
||||
#else
|
||||
#include "platform/platform_win32.cpp"
|
||||
#include "renderer/renderer_dx12.cpp"
|
||||
#include "renderer/renderer_vulkan.cpp"
|
||||
#include "midi/midi_win32.cpp"
|
||||
#include "audio/audio_asio.cpp"
|
||||
#endif
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
|
||||
#include "base/base_core.h"
|
||||
|
||||
struct Renderer;
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Renderer Renderer;
|
||||
struct Clay_RenderCommandArray;
|
||||
|
||||
struct RendererDesc {
|
||||
void *window_handle = nullptr;
|
||||
S32 width = 1280;
|
||||
S32 height = 720;
|
||||
S32 frame_count = 2;
|
||||
};
|
||||
typedef struct RendererDesc {
|
||||
void *window_handle;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
} RendererDesc;
|
||||
|
||||
Renderer *renderer_create(RendererDesc *desc);
|
||||
|
||||
@@ -31,8 +35,11 @@ void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
// Text measurement callback compatible with UI_MeasureTextFn
|
||||
// Measures text of given length (NOT necessarily null-terminated) at font_size pixels.
|
||||
// user_data should be the Renderer pointer.
|
||||
struct Vec2F32;
|
||||
Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void *user_data);
|
||||
|
||||
// Upload an RGBA8 icon atlas texture for icon rendering (4 bytes per pixel)
|
||||
void renderer_create_icon_atlas(Renderer *renderer, const U8 *data, S32 w, S32 h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1772
src/renderer/renderer_vulkan.cpp
Normal file
1772
src/renderer/renderer_vulkan.cpp
Normal file
File diff suppressed because it is too large
Load Diff
61
src/renderer/ui.f.glsl
Normal file
61
src/renderer/ui.f.glsl
Normal file
@@ -0,0 +1,61 @@
|
||||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||
|
||||
layout(location = 0) in vec2 frag_uv;
|
||||
layout(location = 1) in vec4 frag_col;
|
||||
layout(location = 2) in vec2 frag_rect_min;
|
||||
layout(location = 3) in vec2 frag_rect_max;
|
||||
layout(location = 4) in vec4 frag_corner_radii;
|
||||
layout(location = 5) in float frag_border_thickness;
|
||||
layout(location = 6) in float frag_softness;
|
||||
layout(location = 7) in float frag_mode;
|
||||
layout(location = 8) in vec2 frag_pixel_pos;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
float rounded_rect_sdf(vec2 sample_pos, vec2 rect_center, vec2 rect_half_size, float radius) {
|
||||
vec2 d = abs(sample_pos - rect_center) - rect_half_size + vec2(radius, radius);
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 col = frag_col;
|
||||
|
||||
if (frag_mode > 1.5) {
|
||||
// RGBA textured mode: sample all channels, multiply by vertex color
|
||||
vec4 tex_sample = texture(tex, frag_uv);
|
||||
col *= tex_sample;
|
||||
} else if (frag_mode > 0.5) {
|
||||
// Alpha-only textured mode: sample R channel as alpha (font atlas)
|
||||
float alpha = texture(tex, frag_uv).r;
|
||||
col.a *= alpha;
|
||||
} else {
|
||||
// SDF rounded rect mode
|
||||
vec2 pixel_pos = frag_pixel_pos;
|
||||
vec2 rect_center = (frag_rect_min + frag_rect_max) * 0.5;
|
||||
vec2 rect_half_size = (frag_rect_max - frag_rect_min) * 0.5;
|
||||
// corner_radii = (TL, TR, BR, BL) - select radius by quadrant
|
||||
float radius = (pixel_pos.x < rect_center.x)
|
||||
? ((pixel_pos.y < rect_center.y) ? frag_corner_radii.x : frag_corner_radii.w)
|
||||
: ((pixel_pos.y < rect_center.y) ? frag_corner_radii.y : frag_corner_radii.z);
|
||||
float softness = max(frag_softness, 0.5);
|
||||
float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);
|
||||
|
||||
if (frag_border_thickness > 0) {
|
||||
float inner_dist = dist + frag_border_thickness;
|
||||
float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);
|
||||
float inner_alpha = smoothstep(-softness, softness, inner_dist);
|
||||
col.a *= outer_alpha * inner_alpha;
|
||||
} else {
|
||||
col.a *= 1.0 - smoothstep(-softness, softness, dist);
|
||||
}
|
||||
}
|
||||
|
||||
// Dither to reduce gradient banding (interleaved gradient noise)
|
||||
float dither = fract(52.9829189 * fract(dot(frag_pixel_pos, vec2(0.06711056, 0.00583715)))) - 0.5;
|
||||
col.rgb += dither / 255.0;
|
||||
|
||||
if (col.a < 0.002) discard;
|
||||
out_color = col;
|
||||
}
|
||||
43
src/renderer/ui.v.glsl
Normal file
43
src/renderer/ui.v.glsl
Normal file
@@ -0,0 +1,43 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 viewport_size;
|
||||
vec2 _padding;
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 in_pos;
|
||||
layout(location = 1) in vec2 in_uv;
|
||||
layout(location = 2) in vec4 in_col;
|
||||
layout(location = 3) in vec2 in_rect_min;
|
||||
layout(location = 4) in vec2 in_rect_max;
|
||||
layout(location = 5) in vec4 in_corner_radii;
|
||||
layout(location = 6) in float in_border_thickness;
|
||||
layout(location = 7) in float in_softness;
|
||||
layout(location = 8) in float in_mode;
|
||||
|
||||
layout(location = 0) out vec2 frag_uv;
|
||||
layout(location = 1) out vec4 frag_col;
|
||||
layout(location = 2) out vec2 frag_rect_min;
|
||||
layout(location = 3) out vec2 frag_rect_max;
|
||||
layout(location = 4) out vec4 frag_corner_radii;
|
||||
layout(location = 5) out float frag_border_thickness;
|
||||
layout(location = 6) out float frag_softness;
|
||||
layout(location = 7) out float frag_mode;
|
||||
layout(location = 8) out vec2 frag_pixel_pos;
|
||||
|
||||
void main() {
|
||||
vec2 ndc;
|
||||
ndc.x = (in_pos.x / pc.viewport_size.x) * 2.0 - 1.0;
|
||||
ndc.y = (in_pos.y / pc.viewport_size.y) * 2.0 - 1.0; // Vulkan Y is top-down already
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
|
||||
frag_uv = in_uv;
|
||||
frag_col = in_col;
|
||||
frag_rect_min = in_rect_min;
|
||||
frag_rect_max = in_rect_max;
|
||||
frag_corner_radii = in_corner_radii;
|
||||
frag_border_thickness = in_border_thickness;
|
||||
frag_softness = in_softness;
|
||||
frag_mode = in_mode;
|
||||
frag_pixel_pos = in_pos;
|
||||
}
|
||||
Reference in New Issue
Block a user