Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ca2724d
Change format from SRGB to UNORM
IntegratedQuantum Aug 31, 2026
673c868
Assert that push constants use extern structs, nothing else makes sen…
IntegratedQuantum Aug 31, 2026
a379684
Create image view and sampler
IntegratedQuantum Aug 31, 2026
f6502ae
Bind the image to the CommandBuffer
IntegratedQuantum Aug 31, 2026
00f760b
Use vulkan for image drawing and change the interface accordingly
IntegratedQuantum Aug 31, 2026
e93e9b9
Do the OpenGL image and pipeline binding in the OpenGL branch
IntegratedQuantum Aug 31, 2026
6f5e8a6
nine-slice rendering
IntegratedQuantum Aug 31, 2026
81e6f68
custom shaded image
IntegratedQuantum Aug 31, 2026
01dc7a9
Simplify again
IntegratedQuantum Aug 31, 2026
4945aee
Custom shaded rect in vulkan
IntegratedQuantum Aug 31, 2026
5a1112a
Less verbose image binding for CommandBuffer
IntegratedQuantum Aug 31, 2026
8f0cfff
first whatever
Wunka Sep 7, 2026
8479ef4
Merge remote-tracking branch 'Quantum/vulkan_more_images' into vulkan…
Wunka Sep 7, 2026
4fa1c61
nice!
Wunka Sep 7, 2026
cd6edb0
Merge remote-tracking branch 'upstream/master' into vulkanText
Wunka Sep 7, 2026
e39d9e1
wow....
Wunka Sep 8, 2026
1e4514a
remove bufferOffset
Wunka Sep 8, 2026
b6de05d
almost done
Wunka Sep 8, 2026
4bfb401
finally working!
Wunka Sep 8, 2026
382f69f
remove debug print
Wunka Sep 8, 2026
1c887d6
make drawGlyph look better
Wunka Sep 9, 2026
e8078c5
do a few things
Wunka Sep 9, 2026
905daee
do barriers
Wunka Sep 9, 2026
283e360
do commands in one and do less conversion
Wunka Sep 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions assets/cubyz/shaders/graphics/Text.frag
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ layout(location = 1) flat in vec4 color;

layout(binding = 0) uniform sampler2D textureSampler;

#ifdef OPEN_GL
// in pixels
layout(location = 0) uniform vec4 textureRect;
layout(location = 1) uniform vec2 scene;
Expand All @@ -15,6 +16,18 @@ layout(location = 3) uniform float ratio;
layout(location = 4) uniform int fontEffects;
layout(location = 6) uniform vec2 fontSize;
layout(location = 7) uniform vec4 textureBounds;
#else
layout(push_constant, std430) uniform _ {
vec4 textureRect;
vec2 offset;
int fontEffects;
vec4 textureBounds;
vec2 scene;
float ratio;
uint inColor;
vec2 fontSize;
};
#endif

vec2 convert2Proportional(vec2 original, vec2 full){
return vec2(original.x/full.x, original.y/full.y);
Expand Down
13 changes: 13 additions & 0 deletions assets/cubyz/shaders/graphics/Text.vert
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ layout(location = 0) in vec2 face_pos;
layout(location = 0) out vec2 frag_face_pos;
layout(location = 1) flat out vec4 color;

#ifdef OPEN_GL
// in pixels
layout(location = 0) uniform vec4 textureRect;
layout(location = 1) uniform vec2 scene;
layout(location = 2) uniform vec2 offset;
layout(location = 3) uniform float ratio;
layout(location = 4) uniform int fontEffects;
layout(location = 5) uniform uint inColor;
#else
layout(push_constant, std430) uniform _ {
vec4 textureRect;
vec2 offset;
int fontEffects;
vec4 textureBounds;
vec2 scene;
float ratio;
uint inColor;
vec2 fontSize;
};
#endif

vec2 convert2Proportional(vec2 original, vec2 full) {
return vec2(original.x/full.x, original.y/full.y);
Expand Down
220 changes: 170 additions & 50 deletions src/graphics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1083,15 +1083,37 @@ pub const TextBuffer = struct { // MARK: TextBuffer
defer draw.restoreScale(oldScale);
var x: f32 = 0;
var y: f32 = 0;
TextRendering.pipeline.bind(draw.getScissor());
var viewport: [4]c_int = undefined;
c.glGetIntegerv(c.GL_VIEWPORT, &viewport);
c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3]));
c.glUniform1f(TextRendering.uniforms.ratio, draw.scale);
c.glUniform1ui(TextRendering.uniforms.inColor, @bitCast(draw.getColor()));
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0]);
draw.rectVao.bind();

if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.bindPipeline(TextRendering.pipeline, draw.getScissor());
vulkan.currentFrame.guiCommands.pushConstants(TextRendering.pipeline, &TextRendering.CompleteUniforms{
.textureRect = .{0, 0, 0, 0},
.offset = .{0, 0},
.fontEffects = 0,
.textureBounds = .{0, 0, 0, 0},
.scene = .{@floatFromInt(viewport[2]), @floatFromInt(viewport[3])},
.ratio = draw.scale,
.inColor = @bitCast(draw.getColor()),
.fontSize = .{@floatFromInt(TextRendering.textureWidth), @floatFromInt(TextRendering.textureHeight)},
});
vulkan.currentFrame.guiCommands.bindDescriptors(TextRendering.pipeline, .graphics, 0, &.{
.{.image = .{.binding = 0, .image = TextRendering.glyphTexture[0].vulkanImage.?}},
});

vulkan.currentFrame.guiCommands.bindVertexArray(draw.rectVao);
} else {
TextRendering.pipeline.bind(draw.getScissor());

c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3]));
c.glUniform1f(TextRendering.uniforms.ratio, draw.scale);
c.glUniform1ui(TextRendering.uniforms.inColor, @bitCast(draw.getColor()));
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0].textureID);

draw.rectVao.bind();
}
const lineWraps: []f32 = main.stackAllocator.alloc(f32, self.lineBreaks.items.len - 1);
defer main.stackAllocator.free(lineWraps);
var i: usize = 0;
Expand Down Expand Up @@ -1150,15 +1172,36 @@ pub const TextBuffer = struct { // MARK: TextBuffer
defer draw.restoreScale(oldScale);
var x: f32 = 0;
var y: f32 = 0;
TextRendering.pipeline.bind(draw.getScissor());
var viewport: [4]c_int = undefined;
c.glGetIntegerv(c.GL_VIEWPORT, &viewport);
c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3]));
c.glUniform1f(TextRendering.uniforms.ratio, draw.scale);
c.glUniform1ui(TextRendering.uniforms.inColor, @bitCast(draw.getColor()));
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0]);
draw.rectVao.bind();

if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.bindPipeline(TextRendering.pipeline, draw.getScissor());
vulkan.currentFrame.guiCommands.pushConstants(TextRendering.pipeline, &TextRendering.CompleteUniforms{
.textureRect = .{0, 0, 0, 0},
.offset = .{0, 0},
.fontEffects = 0,
.textureBounds = .{0, 0, 0, 0},
.scene = .{@floatFromInt(viewport[2]), @floatFromInt(viewport[3])},
.ratio = draw.scale,
.inColor = @bitCast(draw.getColor()),
.fontSize = .{@floatFromInt(TextRendering.textureWidth), @floatFromInt(TextRendering.textureHeight)},
});
vulkan.currentFrame.guiCommands.bindDescriptors(TextRendering.pipeline, .graphics, 0, &.{
.{.image = .{.binding = 0, .image = TextRendering.glyphTexture[0].vulkanImage.?}},
});

vulkan.currentFrame.guiCommands.bindVertexArray(draw.rectVao);
} else {
TextRendering.pipeline.bind(draw.getScissor());

c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(viewport[2]), @floatFromInt(viewport[3]));
c.glUniform1f(TextRendering.uniforms.ratio, draw.scale);
c.glUniform1ui(TextRendering.uniforms.inColor, @bitCast(draw.getColor()));
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0].textureID);
draw.rectVao.bind();
}
const lineWraps: []f32 = main.stackAllocator.alloc(f32, self.lineBreaks.items.len - 1);
defer main.stackAllocator.free(lineWraps);
var i: usize = 0;
Expand Down Expand Up @@ -1220,13 +1263,31 @@ const TextRendering = struct { // MARK: TextRendering
fontSize: c_int,
} = undefined;

const CompleteUniforms = extern struct {
textureRect: [4]f32 align(16),
offset: [2]f32 align(8),
fontEffects: c_int,
textureBounds: [4]f32 align(16),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
textureRect: [4]f32 align(16),
offset: [2]f32 align(8),
fontEffects: c_int,
textureBounds: [4]f32 align(16),
glyphData: GlyphUniforms,

scene: [2]f32 align(8),
ratio: f32,
inColor: c_uint,
fontSize: [2]f32 align(8),
};

const GlypUniforms = extern struct {
textureRect: [4]f32 align(16),
offset: [2]f32 align(8),
fontEffects: c_int,
textureBounds: [4]f32 align(16),
};

var freetypeLib: c.FT_Library = undefined;
var freetypeFace: c.FT_Face = undefined;
var harfbuzzFace: ?*c.hb_face_t = undefined;
var harfbuzzFont: ?*c.hb_font_t = undefined;
var glyphMapping: main.ListManaged(u31) = undefined;
var glyphData: main.ListManaged(Glyph) = undefined;
var glyphTexture: [2]c_uint = undefined;
var glyphTexture: [2]Texture = undefined;
var textureWidth: i32 = 1024;
const textureHeight: i32 = 16;
var textureOffset: i32 = 0;
Expand All @@ -1247,14 +1308,14 @@ const TextRendering = struct { // MARK: TextRendering
&uniforms,
draw.SimpleVertex2D,
.{
.bindings = &.{.sampler(0, .{.fragment = true})},
.rasterState = .{.cullMode = .none},
.depthStencilState = .{.depthTest = false, .depthWrite = false},
.blendState = .{.attachments = &.{.alphaBlending}, .formats = &.{.{.custom = c.VK_FORMAT_R8_UNORM}}},
.blendState = .{.attachments = &.{.alphaBlending}, .formats = &.{.swapChain}},
.inputAssemblyState = .{.topology = .triangleStrip},
.pushConstantSize = @sizeOf(CompleteUniforms),
},
);
pipeline.bind(null);
errdefer pipeline.deinit();
c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight));
try ftError(c.FT_Init_FreeType(&freetypeLib));
try ftError(c.FT_New_Face(freetypeLib, "assets/cubyz/fonts/unscii-16-full.ttf", 0, &freetypeFace));
try ftError(c.FT_Set_Pixel_Sizes(freetypeFace, 0, textureHeight));
Expand All @@ -1265,51 +1326,85 @@ const TextRendering = struct { // MARK: TextRendering
glyphMapping = .init(main.globalAllocator);
glyphData = .init(main.globalAllocator);
glyphData.append(undefined); // 0 is a reserved value.
c.glGenTextures(2, &glyphTexture);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]);
c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, textureWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[1]);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT);
glyphTexture = .{
.init(),
.init(),
};

if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.bindPipeline(TextRendering.pipeline, null);
errdefer pipeline.deinit();

glyphTexture[0].vulkanImage = vulkan.Image.init(.{textureWidth, textureHeight, 1}, .{
.format = c.VK_FORMAT_R8_UNORM,
.usage = c.VK_IMAGE_USAGE_TRANSFER_DST_BIT | c.VK_IMAGE_USAGE_SAMPLED_BIT,
});
} else {
pipeline.bind(null);
errdefer pipeline.deinit();
c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight));
glyphTexture[0].bind();

c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, textureWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT);
glyphTexture[1].bind();

c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT);
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT);
}
}

fn deinit() void {
pipeline.deinit();
ftError(c.FT_Done_FreeType(freetypeLib)) catch {};
glyphMapping.deinit();
glyphData.deinit();
c.glDeleteTextures(2, &glyphTexture);
glyphTexture[0].deinit();
glyphTexture[1].deinit();
c.hb_font_destroy(harfbuzzFont);
}

fn resizeTexture(newWidth: i32) void {
textureWidth = newWidth;
const swap = glyphTexture[1];
glyphTexture[1] = glyphTexture[0];
glyphTexture[0] = swap;
c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]);
c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, newWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glCopyImageSubData(glyphTexture[1], c.GL_TEXTURE_2D, 0, 0, 0, 0, glyphTexture[0], c.GL_TEXTURE_2D, 0, 0, 0, 0, textureOffset, textureHeight, 1);
pipeline.bind(draw.getScissor());
c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight));
if (main.settings.launchConfig.vulkanTestingMode) {
glyphTexture[0].vulkanImage = vulkan.Image.init(.{textureWidth, textureHeight, 1}, .{
.format = c.VK_FORMAT_R8_UNORM,
.usage = c.VK_IMAGE_USAGE_TRANSFER_DST_BIT | c.VK_IMAGE_USAGE_SAMPLED_BIT,
});
Comment thread
IntegratedQuantum marked this conversation as resolved.
} else {
const swap = glyphTexture[1];
glyphTexture[1] = glyphTexture[0];
glyphTexture[0] = swap;

c.glActiveTexture(c.GL_TEXTURE0);
c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0].textureID);
c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, newWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null);
c.glCopyImageSubData(glyphTexture[1].textureID, c.GL_TEXTURE_2D, 0, 0, 0, 0, glyphTexture[0].textureID, c.GL_TEXTURE_2D, 0, 0, 0, 0, textureOffset, textureHeight, 1);

pipeline.bind(draw.getScissor());
c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight));
}
}

fn uploadData(bitmap: c.FT_Bitmap) void {
const width: i32 = @bitCast(bitmap.width);
const height: i32 = @bitCast(bitmap.rows);
const pitch: i32 = @bitCast(bitmap.pitch);
const buffer = bitmap.buffer orelse return;
if (textureOffset + width > textureWidth) {
resizeTexture(textureWidth*2);
}
c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1);
c.glTexSubImage2D(c.GL_TEXTURE_2D, 0, textureOffset, 0, width, height, c.GL_RED, c.GL_UNSIGNED_BYTE, buffer);
if (main.settings.launchConfig.vulkanTestingMode) {
glyphTexture[0].vulkanImage.?.uploadGlyphs(textureOffset, width, height, bitmap.pitch, buffer[0..@intCast(pitch*height)]);
} else {
c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1);
c.glTexSubImage2D(c.GL_TEXTURE_2D, 0, textureOffset, 0, width, height, c.GL_RED, c.GL_UNSIGNED_BYTE, buffer);
}
textureOffset += width;
}

Expand Down Expand Up @@ -1344,16 +1439,41 @@ const TextRendering = struct { // MARK: TextRendering
y += draw.translation[1];
x = @floor(x);
y = @ceil(y);
c.glUniform1i(uniforms.fontEffects, fontEffects);
c.glUniform4f(uniforms.textureBounds, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1]));

if (fontEffects & 0x1000000 != 0) { // bold
c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x - 1, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y - 1);
c.glUniform4f(uniforms.textureRect, @floatFromInt(glyph.textureX - 1), -1, @floatFromInt(glyph.size[0] + 2), @floatFromInt(glyph.size[1] + 2));
if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.pushConstants(TextRendering.pipeline, &GlypUniforms{
.textureRect = .{@floatFromInt(glyph.textureX - 1), -1, @floatFromInt(glyph.size[0] + 2), @floatFromInt(glyph.size[1] + 2)},
.offset = .{@as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x - 1, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y - 1},
.fontEffects = fontEffects,
.textureBounds = .{@floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1])},
});
} else {
c.glUniform1i(uniforms.fontEffects, fontEffects);
c.glUniform4f(uniforms.textureBounds, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1]));
c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x - 1, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y - 1);
c.glUniform4f(uniforms.textureRect, @floatFromInt(glyph.textureX - 1), -1, @floatFromInt(glyph.size[0] + 2), @floatFromInt(glyph.size[1] + 2));
}
} else {
c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y);
c.glUniform4f(uniforms.textureRect, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1]));
if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.pushConstants(TextRendering.pipeline, &GlypUniforms{
.textureRect = .{@floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1])},
.offset = .{@as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y},
.fontEffects = fontEffects,
.textureBounds = .{@floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1])},
});
} else {
c.glUniform1i(uniforms.fontEffects, fontEffects);
c.glUniform4f(uniforms.textureBounds, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1]));
c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y);
c.glUniform4f(uniforms.textureRect, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1]));
}
}
Comment thread
IntegratedQuantum marked this conversation as resolved.
if (main.settings.launchConfig.vulkanTestingMode) {
vulkan.currentFrame.guiCommands.draw(4, 0);
} else {
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
}
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
}

fn renderText(text: []const u8, x: f32, y: f32, fontSize: f32, initialFontEffect: TextBuffer.FontEffect) void {
Expand Down
Loading
Loading