/// A collection of things that should make dealing with opengl easier. /// Also contains some basic 2d drawing stuff. const std = @import("std"); pub const hbft = @cImport({ @cInclude("freetype/ftadvanc.h"); @cInclude("freetype/ftbbox.h"); @cInclude("freetype/ftbitmap.h"); @cInclude("freetype/ftcolor.h"); @cInclude("freetype/ftlcdfil.h"); @cInclude("freetype/ftsizes.h"); @cInclude("freetype/ftstroke.h"); @cInclude("freetype/fttrigon.h"); @cInclude("freetype/ftsynth.h"); @cInclude("hb.h"); @cInclude("hb-ft.h"); }); const vec = @import("vec.zig"); const Mat4f = vec.Mat4f; const Vec4i = vec.Vec4i; const Vec4f = vec.Vec4f; const Vec2f = vec.Vec2f; const Vec2i = vec.Vec2i; const Vec3f = vec.Vec3f; const main = @import("main.zig"); const Window = main.Window; const Allocator = std.mem.Allocator; pub const c = @cImport ({ @cInclude("glad/glad.h"); }); pub const stb_image = @cImport ({ @cInclude("stb/stb_image.h"); @cInclude("stb/stb_image_write.h"); }); pub const draw = struct { var color: u32 = 0; var clip: ?Vec4i = null; var translation: Vec2f = Vec2f{0, 0}; var scale: f32 = 1; pub fn setColor(newColor: u32) void { color = newColor; } /// Returns the previous translation. pub fn setTranslation(newTranslation: Vec2f) Vec2f { const oldTranslation = translation; translation += newTranslation*@as(Vec2f, @splat(scale)); return oldTranslation; } pub fn restoreTranslation(previousTranslation: Vec2f) void { translation = previousTranslation; } /// Returns the previous scale. pub fn setScale(newScale: f32) f32 { std.debug.assert(newScale >= 0); const oldScale = scale; scale *= newScale; return oldScale; } pub fn restoreScale(previousScale: f32) void { scale = previousScale; } /// Returns the previous clip. pub fn setClip(clipRect: Vec2f) ?Vec4i { std.debug.assert(@reduce(.And, clipRect >= Vec2f{0, 0})); var newClip = Vec4i { std.math.lossyCast(i32, translation[0]), main.Window.height - std.math.lossyCast(i32, translation[1] + clipRect[1]*scale), std.math.lossyCast(i32, clipRect[0]*scale), std.math.lossyCast(i32, clipRect[1]*scale), }; if(clip) |oldClip| { if (newClip[0] < oldClip[0]) { newClip[2] -= oldClip[0] - newClip[0]; newClip[0] += oldClip[0] - newClip[0]; } if (newClip[1] < oldClip[1]) { newClip[3] -= oldClip[1] - newClip[1]; newClip[1] += oldClip[1] - newClip[1]; } if (newClip[0] + newClip[2] > oldClip[0] + oldClip[2]) { newClip[2] -= (newClip[0] + newClip[2]) - (oldClip[0] + oldClip[2]); } if (newClip[1] + newClip[3] > oldClip[1] + oldClip[3]) { newClip[3] -= (newClip[1] + newClip[3]) - (oldClip[1] + oldClip[3]); } newClip[2] = @max(newClip[2], 0); newClip[3] = @max(newClip[3], 0); } else { c.glEnable(c.GL_SCISSOR_TEST); } c.glScissor(newClip[0], newClip[1], newClip[2], newClip[3]); const oldClip = clip; clip = newClip; return oldClip; } /// Should be used to restore the old clip when leaving the render function. pub fn restoreClip(previousClip: ?Vec4i) void { clip = previousClip; if (clip) |clipRef| { c.glScissor(clipRef[0], clipRef[1], clipRef[2], clipRef[3]); } else { c.glDisable(c.GL_SCISSOR_TEST); } } // ---------------------------------------------------------------------------- // Stuff for fillRect: var rectUniforms: struct { screen: c_int, start: c_int, size: c_int, rectColor: c_int, } = undefined; var rectShader: Shader = undefined; pub var rectVAO: c_uint = undefined; var rectVBO: c_uint = undefined; fn initRect() void { rectShader = Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Rect.vs", "assets/cubyz/shaders/graphics/Rect.fs", &rectUniforms) catch Shader{.id = 0}; const rawData = [_]f32 { 0, 0, 0, 1, 1, 0, 1, 1, }; c.glGenVertexArrays(1, &rectVAO); c.glBindVertexArray(rectVAO); c.glGenBuffers(1, &rectVBO); c.glBindBuffer(c.GL_ARRAY_BUFFER, rectVBO); c.glBufferData(c.GL_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW); c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null); c.glEnableVertexAttribArray(0); } fn deinitRect() void { rectShader.deinit(); c.glDeleteVertexArrays(1, &rectVAO); c.glDeleteBuffers(1, &rectVBO); } pub fn rect(_pos: Vec2f, _dim: Vec2f) void { var pos = _pos; var dim = _dim; pos *= @splat(scale); pos += translation; dim *= @splat(scale); rectShader.bind(); c.glUniform2f(rectUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(rectUniforms.start, pos[0], pos[1]); c.glUniform2f(rectUniforms.size, dim[0], dim[1]); c.glUniform1i(rectUniforms.rectColor, @bitCast(color)); c.glBindVertexArray(rectVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } // ---------------------------------------------------------------------------- // Stuff for drawLine: var lineUniforms: struct { screen: c_int, start: c_int, direction: c_int, lineColor: c_int, } = undefined; var lineShader: Shader = undefined; var lineVAO: c_uint = undefined; var lineVBO: c_uint = undefined; fn initLine() void { lineShader = Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Line.vs", "assets/cubyz/shaders/graphics/Line.fs", &lineUniforms) catch Shader{.id = 0}; const rawData = [_]f32 { 0, 0, 1, 1, }; c.glGenVertexArrays(1, &lineVAO); c.glBindVertexArray(lineVAO); c.glGenBuffers(1, &lineVBO); c.glBindBuffer(c.GL_ARRAY_BUFFER, lineVBO); c.glBufferData(c.GL_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW); c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null); c.glEnableVertexAttribArray(0); } fn deinitLine() void { lineShader.deinit(); c.glDeleteVertexArrays(1, &lineVAO); c.glDeleteBuffers(1, &lineVBO); } pub fn line(_pos1: Vec2f, _pos2: Vec2f) void { var pos1 = _pos1; var pos2 = _pos2; pos1 *= @splat(scale); pos1 += translation; pos2 *= @splat(scale); pos2 += translation; lineShader.bind(); c.glUniform2f(lineUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(lineUniforms.start, pos1[0], pos1[1]); c.glUniform2f(lineUniforms.direction, pos2[0] - pos1[0], pos2[1] - pos1[1]); c.glUniform1i(lineUniforms.lineColor, @bitCast(color)); c.glBindVertexArray(lineVAO); c.glDrawArrays(c.GL_LINE_STRIP, 0, 2); } // ---------------------------------------------------------------------------- // Stuff for drawRect: // Draw rect can use the same shader as drawline, because it essentially draws lines. var drawRectVAO: c_uint = undefined; var drawRectVBO: c_uint = undefined; fn initDrawRect() void { const rawData = [_]f32 { 0, 0, 0, 1, 1, 1, 1, 0, }; c.glGenVertexArrays(1, &drawRectVAO); c.glBindVertexArray(drawRectVAO); c.glGenBuffers(1, &drawRectVBO); c.glBindBuffer(c.GL_ARRAY_BUFFER, drawRectVBO); c.glBufferData(c.GL_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW); c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null); c.glEnableVertexAttribArray(0); } fn deinitDrawRect() void { c.glDeleteVertexArrays(1, &drawRectVAO); c.glDeleteBuffers(1, &drawRectVBO); } pub fn rectOutline(_pos: Vec2f, _dim: Vec2f) void { var pos = _pos; var dim = _dim; pos *= @splat(scale); pos += translation; dim *= @splat(scale); lineShader.bind(); c.glUniform2f(lineUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(lineUniforms.start, pos[0], pos[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform2f(lineUniforms.direction, dim[0] - 1, dim[1] - 1); // The height is a lot smaller because the inner edge of the rect is drawn. c.glUniform1i(lineUniforms.lineColor, @bitCast(color)); c.glBindVertexArray(lineVAO); c.glDrawArrays(c.GL_LINE_LOOP, 0, 5); } // ---------------------------------------------------------------------------- // Stuff for fillCircle: var circleUniforms: struct { screen: c_int, center: c_int, radius: c_int, circleColor: c_int, } = undefined; var circleShader: Shader = undefined; var circleVAO: c_uint = undefined; var circleVBO: c_uint = undefined; fn initCircle() void { circleShader = Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Circle.vs", "assets/cubyz/shaders/graphics/Circle.fs", &circleUniforms) catch Shader{.id = 0}; const rawData = [_]f32 { -1, -1, -1, 1, 1, -1, 1, 1, }; c.glGenVertexArrays(1, &circleVAO); c.glBindVertexArray(circleVAO); c.glGenBuffers(1, &circleVBO); c.glBindBuffer(c.GL_ARRAY_BUFFER, circleVBO); c.glBufferData(c.GL_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW); c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null); c.glEnableVertexAttribArray(0); } fn deinitCircle() void { circleShader.deinit(); c.glDeleteVertexArrays(1, &circleVAO); c.glDeleteBuffers(1, &circleVBO); } pub fn circle(_center: Vec2f, _radius: f32) void { var center = _center; var radius = _radius; _center *= @splat(scale); _center += translation; radius *= scale; circleShader.bind(); c.glUniform2f(circleUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(circleUniforms.center, center[0], center[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform1f(circleUniforms.radius, radius); // The height is a lot smaller because the inner edge of the rect is drawn. c.glUniform1i(circleUniforms.circleColor, @bitCast(color)); c.glBindVertexArray(circleVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } // ---------------------------------------------------------------------------- // Stuff for drawImage: // Luckily the vao of the regular rect can used. var imageUniforms: struct { screen: c_int, start: c_int, size: c_int, image: c_int, color: c_int, } = undefined; var imageShader: Shader = undefined; fn initImage() void { imageShader = Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Image.vs", "assets/cubyz/shaders/graphics/Image.fs", &imageUniforms) catch Shader{.id = 0}; } fn deinitImage() void { imageShader.deinit(); } pub fn boundImage(_pos: Vec2f, _dim: Vec2f) void { var pos = _pos; var dim = _dim; pos *= @splat(scale); pos += translation; dim *= @splat(scale); pos = @floor(pos); dim = @ceil(dim); imageShader.bind(); c.glUniform2f(imageUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(imageUniforms.start, pos[0], pos[1]); c.glUniform2f(imageUniforms.size, dim[0], dim[1]); c.glUniform1i(imageUniforms.color, @bitCast(color)); c.glBindVertexArray(rectVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } // ---------------------------------------------------------------------------- pub fn customShadedRect(uniforms: anytype, _pos: Vec2f, _dim: Vec2f) void { var pos = _pos; var dim = _dim; pos *= @splat(scale); pos += translation; dim *= @splat(scale); pos = @floor(pos); dim = @ceil(dim); c.glUniform2f(uniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height)); c.glUniform2f(uniforms.start, pos[0], pos[1]); c.glUniform2f(uniforms.size, dim[0], dim[1]); c.glUniform1i(uniforms.color, @bitCast(color)); c.glUniform1f(uniforms.scale, scale); c.glBindVertexArray(rectVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } // ---------------------------------------------------------------------------- pub fn text(_text: []const u8, x: f32, y: f32, fontSize: f32, alignment: TextBuffer.Alignment) !void { try TextRendering.renderText(_text, x, y, fontSize, .{.color = @truncate(@as(u32, @bitCast(color)))}, alignment); } pub inline fn print(comptime format: []const u8, args: anytype, x: f32, y: f32, fontSize: f32, alignment: TextBuffer.Alignment) !void { var stackFallback = std.heap.stackFallback(4096, main.threadAllocator); const allocator = stackFallback.get(); const string = try std.fmt.allocPrint(allocator, format, args); defer allocator.free(string); try text(string, x, y ,fontSize, alignment); } }; pub const TextBuffer = struct { pub const Alignment = enum { left, center, right, }; pub const FontEffect = packed struct(u28) { color: u24 = 0xffffff, bold: bool = false, italic: bool = false, underline: bool = false, strikethrough: bool = false, fn hasLine(self: FontEffect, comptime isUnderline: bool) bool { if(isUnderline) return self.underline; return self.strikethrough; } }; const Line = struct { start: f32, end: f32, color: u24, isUnderline: bool, }; const LineBreak = struct { index: u32, width: f32, }; const GlyphData = struct { x_advance: f32, y_advance: f32, x_offset: f32, y_offset: f32, character: u21, index: u32, cluster: u32, fontEffect: FontEffect, characterIndex: u32, }; alignment: Alignment, width: f32, buffer: ?*hbft.hb_buffer_t, glyphs: []GlyphData, lines: std.ArrayList(Line), lineBreaks: std.ArrayList(LineBreak), fn addLine(self: *TextBuffer, line: Line) !void { if(line.start != line.end) { try self.lines.append(line); } } fn initLines(self: *TextBuffer, comptime isUnderline: bool) !void { var line: Line = Line {.start = 0, .end = 0, .color = 0, .isUnderline = isUnderline}; var lastFontEffect: FontEffect = .{}; for(self.glyphs) |glyph| { const fontEffect = glyph.fontEffect; if(lastFontEffect.hasLine(isUnderline)) { if(fontEffect.color != lastFontEffect.color) { try self.addLine(line); line.color = fontEffect.color; line.start = line.end; } else if(!fontEffect.hasLine(isUnderline)) { try self.addLine(line); } } else if(fontEffect.hasLine(isUnderline)) { line.start = line.end; line.color = fontEffect.color; } lastFontEffect = fontEffect; line.end += glyph.x_advance; } if(lastFontEffect.hasLine(isUnderline)) { try self.addLine(line); } } const Parser = struct { unicodeIterator: std.unicode.Utf8Iterator, currentFontEffect: FontEffect, parsedText: std.ArrayList(u32), fontEffects: std.ArrayList(FontEffect), characterIndex: std.ArrayList(u32), showControlCharacters: bool, curChar: u21 = undefined, curIndex: u32 = 0, fn appendControlGetNext(self: *Parser) !?void { if(self.showControlCharacters) { try self.fontEffects.append(.{.color = 0x808080}); try self.parsedText.append(self.curChar); try self.characterIndex.append(self.curIndex); } self.curIndex = @intCast(self.unicodeIterator.i); self.curChar = self.unicodeIterator.nextCodepoint() orelse return null; } fn appendGetNext(self: *Parser) !?void { try self.fontEffects.append(self.currentFontEffect); try self.parsedText.append(self.curChar); try self.characterIndex.append(self.curIndex); self.curIndex = @intCast(self.unicodeIterator.i); self.curChar = self.unicodeIterator.nextCodepoint() orelse return null; } fn parse(self: *Parser) !void { self.curIndex = @intCast(self.unicodeIterator.i); self.curChar = self.unicodeIterator.nextCodepoint() orelse return; while(true) switch(self.curChar) { '*' => { try self.appendControlGetNext() orelse return; if(self.curChar == '*') { try self.appendControlGetNext() orelse return; self.currentFontEffect.bold = !self.currentFontEffect.bold; } else { self.currentFontEffect.italic = !self.currentFontEffect.italic; } }, '_' => { try self.appendControlGetNext() orelse return; if(self.curChar == '_') { try self.appendControlGetNext() orelse return; self.currentFontEffect.strikethrough = !self.currentFontEffect.strikethrough; } else { self.currentFontEffect.underline = !self.currentFontEffect.underline; } }, '\\' => { try self.appendControlGetNext() orelse return; try self.appendGetNext() orelse return; }, '#' => { try self.appendControlGetNext() orelse return; var shift: u5 = 20; while(true) : (shift -= 4) { self.currentFontEffect.color = (self.currentFontEffect.color & ~(@as(u24, 0xf) << shift)) | @as(u24, switch(self.curChar) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => self.curChar - '0', 'a', 'b', 'c', 'd', 'e', 'f' => self.curChar - 'a' + 10, 'A', 'B', 'C', 'D', 'E', 'F' => self.curChar - 'A' + 10, else => 0, }) << shift; try self.appendControlGetNext() orelse return; if(shift == 0) break; } }, else => { try self.appendGetNext() orelse return; } }; } }; pub fn init(allocator: Allocator, text: []const u8, initialFontEffect: FontEffect, showControlCharacters: bool, alignment: Alignment) Allocator.Error!TextBuffer { var self: TextBuffer = undefined; self.alignment = alignment; var stackFallback = std.heap.stackFallback(4096, main.threadAllocator); const stackFallbackAllocator = stackFallback.get(); // Parse the input text: var parser = Parser { .unicodeIterator = std.unicode.Utf8Iterator{.bytes = text, .i = 0}, .currentFontEffect = initialFontEffect, .parsedText = std.ArrayList(u32).init(stackFallbackAllocator), .fontEffects = std.ArrayList(FontEffect).init(allocator), .characterIndex = std.ArrayList(u32).init(allocator), .showControlCharacters = showControlCharacters }; defer parser.fontEffects.deinit(); defer parser.parsedText.deinit(); defer parser.characterIndex.deinit(); self.lines = std.ArrayList(Line).init(allocator); self.lineBreaks = std.ArrayList(LineBreak).init(allocator); try parser.parse(); if(parser.parsedText.items.len == 0) { self.glyphs = &[0]GlyphData{}; return self; } // Let harfbuzz do its thing: const buffer = hbft.hb_buffer_create() orelse return error.OutOfMemory; defer hbft.hb_buffer_destroy(buffer); hbft.hb_buffer_add_utf32(buffer, parser.parsedText.items.ptr, @intCast(parser.parsedText.items.len), 0, @intCast(parser.parsedText.items.len)); hbft.hb_buffer_set_direction(buffer, hbft.HB_DIRECTION_LTR); hbft.hb_buffer_set_script(buffer, hbft.HB_SCRIPT_COMMON); hbft.hb_buffer_set_language(buffer, hbft.hb_language_get_default()); hbft.hb_shape(TextRendering.harfbuzzFont, buffer, null, 0); var glyphInfos: []hbft.hb_glyph_info_t = undefined; var glyphPositions: []hbft.hb_glyph_position_t = undefined; { var len: c_uint = 0; glyphInfos.ptr = hbft.hb_buffer_get_glyph_infos(buffer, &len).?; glyphPositions.ptr = hbft.hb_buffer_get_glyph_positions(buffer, &len).?; glyphInfos.len = len; glyphPositions.len = len; } // Guess the text index from the given cluster indices. Only works if the number of glyphs and the number of characters in a cluster is the same. const textIndexGuess = try stackFallbackAllocator.alloc(u32, glyphInfos.len); defer stackFallbackAllocator.free(textIndexGuess); for(textIndexGuess, 0..) |*index, i| { if(i == 0 or glyphInfos[i-1].cluster != glyphInfos[i].cluster) { index.* = glyphInfos[i].cluster; } else { index.* = @min(textIndexGuess[i-1] + 1, @as(u32, @intCast(parser.parsedText.items.len-1))); for(glyphInfos[i..]) |glyphInfo| { if(glyphInfo.cluster != glyphInfos[i].cluster) { index.* = @min(index.*, glyphInfo.cluster - 1); break; } } } } // Merge it all together: self.glyphs = try allocator.alloc(GlyphData, glyphInfos.len); for(self.glyphs, 0..) |*glyph, i| { glyph.x_advance = @as(f32, @floatFromInt(glyphPositions[i].x_advance))/4.0; glyph.y_advance = @as(f32, @floatFromInt(glyphPositions[i].y_advance))/4.0; glyph.x_offset = @as(f32, @floatFromInt(glyphPositions[i].x_offset))/4.0; glyph.y_offset = @as(f32, @floatFromInt(glyphPositions[i].y_offset))/4.0; glyph.character = @intCast(parser.parsedText.items[textIndexGuess[i]]); glyph.index = glyphInfos[i].codepoint; glyph.cluster = glyphInfos[i].cluster; glyph.fontEffect = parser.fontEffects.items[textIndexGuess[i]]; glyph.characterIndex = parser.characterIndex.items[textIndexGuess[i]]; } // Find the lines: try self.initLines(true); try self.initLines(false); try self.lineBreaks.append(.{.index = 0, .width = 0}); try self.lineBreaks.append(.{.index = @intCast(self.glyphs.len), .width = 0}); return self; } pub fn deinit(self: TextBuffer) void { self.lines.allocator.free(self.glyphs); self.lines.deinit(); self.lineBreaks.deinit(); } fn getLineOffset(self: TextBuffer, line: usize) f32 { const factor: f32 = switch(self.alignment) { .left => 0, .center => 0.5, .right => 1, }; const diff = self.width - self.lineBreaks.items[line+1].width; return diff*factor; } pub fn mousePosToIndex(self: TextBuffer, mousePos: Vec2f, bufferLen: usize) u32 { var line: usize = @intFromFloat(@max(0, mousePos[1]/16.0)); line = @min(line, self.lineBreaks.items.len - 2); var x: f32 = self.getLineOffset(line); const start = self.lineBreaks.items[line].index; const end = self.lineBreaks.items[line + 1].index; for(self.glyphs[start..end]) |glyph| { if(mousePos[0] < x + glyph.x_advance/2) { return @intCast(glyph.characterIndex); } x += glyph.x_advance; } return @intCast(if(end < self.glyphs.len) self.glyphs[end-1].characterIndex else bufferLen); } pub fn indexToCursorPos(self: TextBuffer, index: u32) Vec2f { var x: f32 = 0; var y: f32 = 0; var i: usize = 0; while(true) { x = self.getLineOffset(i); for(self.glyphs[self.lineBreaks.items[i].index..self.lineBreaks.items[i+1].index]) |glyph| { if(glyph.characterIndex == index) { return .{x, y}; } x += glyph.x_advance; y -= glyph.y_advance; } i += 1; if(i >= self.lineBreaks.items.len - 1) { return .{x, y}; } y += 16; } } /// Returns the calculated dimensions of the text block. pub fn calculateLineBreaks(self: *TextBuffer, fontSize: f32, maxLineWidth: f32) !Vec2f { self.lineBreaks.clearRetainingCapacity(); const spaceCharacterWidth = 8; try self.lineBreaks.append(.{.index = 0, .width = 0}); const scaledMaxWidth = maxLineWidth/fontSize*16.0; var lineWidth: f32 = 0; var lastSpaceWidth: f32 = 0; var lastSpaceIndex: u32 = 0; for(self.glyphs, 0..) |glyph, i| { lineWidth += glyph.x_advance; if(glyph.character == ' ') { lastSpaceWidth = lineWidth; lastSpaceIndex = @intCast(i+1); } if(glyph.character == '\n') { try self.lineBreaks.append(.{.index = @intCast(i+1), .width = lineWidth - spaceCharacterWidth}); lineWidth = 0; lastSpaceIndex = 0; lastSpaceWidth = 0; } if(lineWidth > scaledMaxWidth) { if(lastSpaceIndex != 0) { lineWidth -= lastSpaceWidth; try self.lineBreaks.append(.{.index = lastSpaceIndex, .width = lastSpaceWidth - spaceCharacterWidth}); lastSpaceIndex = 0; lastSpaceWidth = 0; } else { try self.lineBreaks.append(.{.index = @intCast(i), .width = lineWidth - glyph.x_advance}); lineWidth = glyph.x_advance; lastSpaceIndex = 0; lastSpaceWidth = 0; } } } self.width = maxLineWidth; try self.lineBreaks.append(.{.index = @intCast(self.glyphs.len), .width = lineWidth}); return Vec2f{maxLineWidth*fontSize/16.0, @as(f32, @floatFromInt(self.lineBreaks.items.len - 1))*fontSize}; } pub fn drawSelection(self: TextBuffer, pos: Vec2f, selectionStart: u32, selectionEnd: u32) !void { std.debug.assert(selectionStart <= selectionEnd); var x: f32 = self.getLineOffset(0); var y: f32 = 0; var i: usize = 0; var j: usize = 0; // Find the start row: outer: while(i < self.lineBreaks.items.len - 1) : (i += 1) { x = self.getLineOffset(i); while(j < self.lineBreaks.items[i+1].index) : (j += 1) { const glyph = self.glyphs[j]; if(glyph.characterIndex >= selectionStart) break :outer; x += glyph.x_advance; y -= glyph.y_advance; } y += 16; } while(i < self.lineBreaks.items.len - 1) { const startX = x; while(j < self.lineBreaks.items[i+1].index and j < selectionEnd) : (j += 1) { const glyph = self.glyphs[j]; if(glyph.characterIndex >= selectionEnd) break; x += glyph.x_advance; y -= glyph.y_advance; } draw.rect(pos + Vec2f{startX, y}, .{x - startX, 16}); i += 1; if(i >= self.lineBreaks.items.len - 1) break; x = self.getLineOffset(i); y += 16; } } pub fn render(self: TextBuffer, _x: f32, _y: f32, _fontSize: f32) !void { try self.renderShadow(_x, _y, _fontSize); const oldTranslation = draw.setTranslation(.{_x, _y}); defer draw.restoreTranslation(oldTranslation); const oldScale = draw.setScale(_fontSize/16.0); defer draw.restoreScale(oldScale); var x: f32 = 0; var y: f32 = 0; TextRendering.shader.bind(); c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(main.Window.width), @floatFromInt(main.Window.height)); c.glUniform1f(TextRendering.uniforms.ratio, draw.scale); c.glUniform1f(TextRendering.uniforms.alpha, @as(f32, @floatFromInt(draw.color >> 24)) / 255.0); c.glActiveTexture(c.GL_TEXTURE0); c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0]); c.glBindVertexArray(draw.rectVAO); var stackFallback = std.heap.stackFallback(4096, main.threadAllocator); const allocator = stackFallback.get(); const lineWraps: []f32 = try allocator.alloc(f32, self.lineBreaks.items.len - 1); defer allocator.free(lineWraps); var i: usize = 0; while(i < self.lineBreaks.items.len - 1) : (i += 1) { x = self.getLineOffset(i); for(self.glyphs[self.lineBreaks.items[i].index..self.lineBreaks.items[i+1].index]) |glyph| { if(glyph.character != '\n') { const ftGlyph = try TextRendering.getGlyph(glyph.index); TextRendering.drawGlyph(ftGlyph, x + glyph.x_offset, y - glyph.y_offset, @bitCast(glyph.fontEffect)); } x += glyph.x_advance; y -= glyph.y_advance; } lineWraps[i] = x - self.getLineOffset(i); x = 0; y += 16; } for(self.lines.items) |_line| { var line: Line = _line; y = 0; if(line.isUnderline) y += 15 else y += 8; draw.setColor(line.color | (@as(u32, 0xff000000) & draw.color)); for(lineWraps, 0..) |lineWrap, j| { const lineStart = @max(0, line.start); const lineEnd = @min(lineWrap, line.end); if(lineStart < lineEnd) { const start = Vec2f{lineStart + self.getLineOffset(j), y}; const dim = Vec2f{lineEnd - lineStart, 1}; draw.rect(start, dim); } line.start -= lineWrap; line.end -= lineWrap; y += 16; } } } fn shadowColor(color: u24) u24 { const r: f32 = @floatFromInt(color >> 16); const g: f32 = @floatFromInt(color >> 8 & 255); const b: f32 = @floatFromInt(color & 255); const perceivedBrightness = @sqrt(0.299*r*r + 0.587*g*g + 0.114*b*b); if(perceivedBrightness < 64) { return 0xffffff; // Make shadows white for better readability. } else { return 0; } } fn renderShadow(self: TextBuffer, _x: f32, _y: f32, _fontSize: f32) !void { // Basically a copy of render with some color and position changes. const oldTranslation = draw.setTranslation(.{_x + _fontSize/16.0, _y + _fontSize/16.0}); defer draw.restoreTranslation(oldTranslation); const oldScale = draw.setScale(_fontSize/16.0); defer draw.restoreScale(oldScale); var x: f32 = 0; var y: f32 = 0; TextRendering.shader.bind(); c.glUniform2f(TextRendering.uniforms.scene, @floatFromInt(main.Window.width), @floatFromInt(main.Window.height)); c.glUniform1f(TextRendering.uniforms.ratio, draw.scale); c.glUniform1f(TextRendering.uniforms.alpha, @as(f32, @floatFromInt(draw.color >> 24)) / 255.0); c.glActiveTexture(c.GL_TEXTURE0); c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0]); c.glBindVertexArray(draw.rectVAO); var stackFallback = std.heap.stackFallback(4096, main.threadAllocator); const allocator = stackFallback.get(); const lineWraps: []f32 = try allocator.alloc(f32, self.lineBreaks.items.len - 1); defer allocator.free(lineWraps); var i: usize = 0; while(i < self.lineBreaks.items.len - 1) : (i += 1) { x = self.getLineOffset(i); for(self.glyphs[self.lineBreaks.items[i].index..self.lineBreaks.items[i+1].index]) |glyph| { if(glyph.character != '\n') { const ftGlyph = try TextRendering.getGlyph(glyph.index); var fontEffect = glyph.fontEffect; fontEffect.color = shadowColor(fontEffect.color); TextRendering.drawGlyph(ftGlyph, x + glyph.x_offset, y - glyph.y_offset, @bitCast(fontEffect)); } x += glyph.x_advance; y -= glyph.y_advance; } lineWraps[i] = x - self.getLineOffset(i); x = 0; y += 16; } for(self.lines.items) |_line| { var line: Line = _line; y = 0; if(line.isUnderline) y += 15 else y += 8; draw.setColor(shadowColor(line.color) | (@as(u32, 0xff000000) & draw.color)); for(lineWraps, 0..) |lineWrap, j| { const lineStart = @max(0, line.start); const lineEnd = @min(lineWrap, line.end); if(lineStart < lineEnd) { const start = Vec2f{lineStart + self.getLineOffset(j), y}; const dim = Vec2f{lineEnd - lineStart, 1}; draw.rect(start, dim); } line.start -= lineWrap; line.end -= lineWrap; y += 16; } } } }; const TextRendering = struct { const Glyph = struct { textureX: i32, size: Vec2i, bearing: Vec2i, advance: f32, }; var shader: Shader = undefined; var uniforms: struct { texture_rect: c_int, scene: c_int, offset: c_int, ratio: c_int, fontEffects: c_int, fontSize: c_int, texture_sampler: c_int, alpha: c_int, } = undefined; var freetypeLib: hbft.FT_Library = undefined; var freetypeFace: hbft.FT_Face = undefined; var harfbuzzFace: ?*hbft.hb_face_t = undefined; var harfbuzzFont: ?*hbft.hb_font_t = undefined; var glyphMapping: std.ArrayList(u31) = undefined; var glyphData: std.ArrayList(Glyph) = undefined; var glyphTexture: [2]c_uint = undefined; var textureWidth: i32 = 1024; const textureHeight: i32 = 16; var textureOffset: i32 = 0; fn ftError(errorCode: hbft.FT_Error) !void { if(errorCode == 0) return; const errorString = hbft.FT_Error_String(errorCode); std.log.err("Got freetype error {s}", .{errorString}); return error.freetype; } fn init() !void { shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Text.vs", "assets/cubyz/shaders/graphics/Text.fs", &uniforms); shader.bind(); c.glUniform1i(uniforms.texture_sampler, 0); c.glUniform1f(uniforms.alpha, 1.0); c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight)); try ftError(hbft.FT_Init_FreeType(&freetypeLib)); try ftError(hbft.FT_New_Face(freetypeLib, "assets/cubyz/fonts/unscii-16-full.ttf", 0, &freetypeFace)); try ftError(hbft.FT_Set_Pixel_Sizes(freetypeFace, 0, textureHeight)); harfbuzzFace = hbft.hb_ft_face_create_referenced(freetypeFace); harfbuzzFont = hbft.hb_font_create(harfbuzzFace); glyphMapping = std.ArrayList(u31).init(main.globalAllocator); glyphData = std.ArrayList(Glyph).init(main.globalAllocator); try 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); } fn deinit() void { shader.deinit(); ftError(hbft.FT_Done_FreeType(freetypeLib)) catch {}; glyphMapping.deinit(); glyphData.deinit(); c.glDeleteTextures(2, &glyphTexture); hbft.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 ); shader.bind(); c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight)); } fn uploadData(bitmap: hbft.FT_Bitmap) !void { const width: i32 = @bitCast(bitmap.width); const height: i32 = @bitCast(bitmap.rows); const buffer = bitmap.buffer orelse return; if(textureOffset + width > textureWidth) { try 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); textureOffset += width; } fn getGlyph(index: u32) !Glyph { if(index >= glyphMapping.items.len) { try glyphMapping.appendNTimes(0, index - glyphMapping.items.len + 1); } if(glyphMapping.items[index] == 0) {// glyph was not initialized yet. try ftError(hbft.FT_Load_Glyph(freetypeFace, index, hbft.FT_LOAD_RENDER)); const glyph = freetypeFace.*.glyph; const bitmap = glyph.*.bitmap; const width = bitmap.width; const height = bitmap.rows; glyphMapping.items[index] = @intCast(glyphData.items.len); (try glyphData.addOne()).* = Glyph { .textureX = textureOffset, .size = Vec2i{@intCast(width), @intCast(height)}, .bearing = Vec2i{glyph.*.bitmap_left, 16 - glyph.*.bitmap_top}, .advance = @as(f32, @floatFromInt(glyph.*.advance.x))/@as(f32, 1 << 6), }; try uploadData(bitmap); } return glyphData.items[glyphMapping.items[index]]; } fn drawGlyph(glyph: Glyph, _x: f32, _y: f32, fontEffects: u28) void { var x = _x; var y = _y; x *= draw.scale; y *= draw.scale; x += draw.translation[0]; y += draw.translation[1]; x = @floor(x); y = @ceil(y); c.glUniform1i(uniforms.fontEffects, fontEffects); if(fontEffects & 0x1000000 != 0) { // bold c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y - 1); c.glUniform4f(uniforms.texture_rect, @floatFromInt(glyph.textureX), -1, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1] + 1)); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); // Just draw another thing on top in x direction. The y-direction is handled in the shader. c.glUniform2f(uniforms.offset, @as(f32, @floatFromInt(glyph.bearing[0]))*draw.scale + x + 0.5, @as(f32, @floatFromInt(glyph.bearing[1]))*draw.scale + y - 1); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } 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.texture_rect, @floatFromInt(glyph.textureX), 0, @floatFromInt(glyph.size[0]), @floatFromInt(glyph.size[1])); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } } fn renderText(text: []const u8, x: f32, y: f32, fontSize: f32, initialFontEffect: TextBuffer.FontEffect, alignment: TextBuffer.Alignment) !void { var stackFallback = std.heap.stackFallback(4096, main.threadAllocator); const allocator = stackFallback.get(); const buf = try TextBuffer.init(allocator, text, initialFontEffect, false, alignment); defer buf.deinit(); try buf.render(x, y, fontSize); } }; pub fn init() !void { draw.initCircle(); draw.initDrawRect(); draw.initImage(); draw.initLine(); draw.initRect(); try TextRendering.init(); try block_texture.init(); } pub fn deinit() void { draw.deinitCircle(); draw.deinitDrawRect(); draw.deinitImage(); draw.deinitLine(); draw.deinitRect(); TextRendering.deinit(); block_texture.deinit(); } pub const Shader = struct { id: c_uint, fn addShader(self: *const Shader, filename: []const u8, shader_stage: c_uint) !void { const source = main.files.read(main.threadAllocator, filename) catch |err| { std.log.warn("Couldn't find file: {s}", .{filename}); return err; }; defer main.threadAllocator.free(source); const shader = c.glCreateShader(shader_stage); defer c.glDeleteShader(shader); const sourceLen: c_int = @intCast(source.len); c.glShaderSource(shader, 1, &source.ptr, &sourceLen); c.glCompileShader(shader); var success: c_int = undefined; c.glGetShaderiv(shader, c.GL_COMPILE_STATUS, &success); if(success != c.GL_TRUE) { var len: u32 = undefined; c.glGetShaderiv(shader, c.GL_INFO_LOG_LENGTH, @ptrCast(&len)); var buf: [4096] u8 = undefined; c.glGetShaderInfoLog(shader, 4096, @ptrCast(&len), &buf); std.log.err("Error compiling shader {s}:\n{s}\n", .{filename, buf[0..len]}); return error.FailedCompiling; } c.glAttachShader(self.id, shader); } fn link(self: *const Shader) !void { c.glLinkProgram(self.id); var success: c_int = undefined; c.glGetProgramiv(self.id, c.GL_LINK_STATUS, &success); if(success != c.GL_TRUE) { var len: u32 = undefined; c.glGetProgramiv(self.id, c.GL_INFO_LOG_LENGTH, @ptrCast(&len)); var buf: [4096] u8 = undefined; c.glGetProgramInfoLog(self.id, 4096, @ptrCast(&len), &buf); std.log.err("Error Linking Shader program:\n{s}\n", .{buf[0..len]}); return error.FailedLinking; } } pub fn init(vertex: []const u8, fragment: []const u8) !Shader { const shader = Shader{.id = c.glCreateProgram()}; try shader.addShader(vertex, c.GL_VERTEX_SHADER); try shader.addShader(fragment, c.GL_FRAGMENT_SHADER); try shader.link(); return shader; } pub fn initAndGetUniforms(vertex: []const u8, fragment: []const u8, ptrToUniformStruct: anytype) !Shader { const self = try Shader.init(vertex, fragment); inline for(@typeInfo(@TypeOf(ptrToUniformStruct.*)).Struct.fields) |field| { if(field.type == c_int) { @field(ptrToUniformStruct, field.name) = c.glGetUniformLocation(self.id, field.name[0..] ++ "\x00"); // TODO: #16072 } } return self; } pub fn initCompute(compute: []const u8) !Shader { const shader = Shader{.id = c.glCreateProgram()}; try shader.addShader(compute, c.GL_COMPUTE_SHADER); try shader.link(); return shader; } pub fn initComputeAndGetUniforms(compute: []const u8, ptrToUniformStruct: anytype) !Shader { const self = try Shader.initCompute(compute); inline for(@typeInfo(@TypeOf(ptrToUniformStruct.*)).Struct.fields) |field| { if(field.type == c_int) { @field(ptrToUniformStruct, field.name) = c.glGetUniformLocation(self.id, field.name[0..] ++ "\x00"); // TODO: #16072 } } return self; } pub fn bind(self: *const Shader) void { c.glUseProgram(self.id); } pub fn deinit(self: *const Shader) void { c.glDeleteProgram(self.id); } }; pub const SSBO = struct { bufferID: c_uint, pub fn init() SSBO { var self = SSBO{.bufferID = undefined}; c.glGenBuffers(1, &self.bufferID); return self; } pub fn initStatic(comptime T: type, data: []const T) SSBO { var self = SSBO{.bufferID = undefined}; c.glGenBuffers(1, &self.bufferID); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID); c.glBufferStorage(c.GL_SHADER_STORAGE_BUFFER, @intCast(data.len*@sizeOf(T)), data.ptr, 0); return self; } pub fn deinit(self: SSBO) void { c.glDeleteBuffers(1, &self.bufferID); } pub fn bind(self: SSBO, binding: c_uint) void { c.glBindBufferBase(c.GL_SHADER_STORAGE_BUFFER, binding, self.bufferID); } pub fn bufferData(self: SSBO, comptime T: type, data: []const T) void { c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID); c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(data.len*@sizeOf(T)), data.ptr, c.GL_STATIC_DRAW); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); } pub fn createDynamicBuffer(self: SSBO, size: usize) void { c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID); c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(size), null, c.GL_DYNAMIC_DRAW); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); } }; pub const SubAllocation = struct { start: u31, len: u31, }; /// A big SSBO that is able to allocate/free smaller regions. pub fn LargeBuffer(comptime Entry: type) type { return struct { ssbo: SSBO, freeBlocks: std.ArrayList(SubAllocation), persistentBuffer: [*]align(64) Entry, // glMapBufferRange has a guaranteed alignment of at least 64 bytes. fences: [3]c.GLsync, fencedFreeLists: [3]std.ArrayList(SubAllocation), activeFence: u8, capacity: u31, used: u32, binding: c_uint, const Self = @This(); fn createBuffer(self: *Self, size: u31) void { self.ssbo = SSBO.init(); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.ssbo.bufferID); const flags = c.GL_MAP_WRITE_BIT | c.GL_MAP_PERSISTENT_BIT | c.GL_MAP_COHERENT_BIT; const bytes = @as(c_long, size)*@sizeOf(Entry); c.glBufferStorage(c.GL_SHADER_STORAGE_BUFFER, bytes, null, flags); self.persistentBuffer = @ptrCast(@alignCast(c.glMapBufferRange(c.GL_SHADER_STORAGE_BUFFER, 0, bytes, flags | c.GL_MAP_INVALIDATE_BUFFER_BIT).?)); self.ssbo.bind(self.binding); self.capacity = size; } pub fn init(self: *Self, allocator: Allocator, size: u31, binding: c_uint) !void { self.binding = binding; self.createBuffer(size); self.activeFence = 0; for(&self.fences) |*fence| { fence.* = c.glFenceSync(c.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } for(&self.fencedFreeLists) |*list| { list.* = std.ArrayList(SubAllocation).init(allocator); } self.freeBlocks = std.ArrayList(SubAllocation).init(allocator); try self.freeBlocks.append(.{.start = 0, .len = size}); } pub fn deinit(self: *Self) void { for(self.fences) |fence| { c.glDeleteSync(fence); } for(self.fencedFreeLists) |list| { list.deinit(); } c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.ssbo.bufferID); _ = c.glUnmapBuffer(c.GL_SHADER_STORAGE_BUFFER); self.ssbo.deinit(); self.freeBlocks.deinit(); } pub fn beginRender(self: *Self) !void { self.activeFence += 1; if(self.activeFence == self.fences.len) self.activeFence = 0; for(self.fencedFreeLists[self.activeFence].items) |allocation| { try self.finalFree(allocation); } self.fencedFreeLists[self.activeFence].clearRetainingCapacity(); _ = c.glClientWaitSync(self.fences[self.activeFence], 0, c.GL_TIMEOUT_IGNORED); // Make sure the render calls that accessed these parts of the buffer have finished. } pub fn endRender(self: *Self) void { c.glDeleteSync(self.fences[self.activeFence]); self.fences[self.activeFence] = c.glFenceSync(c.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } fn alloc(self: *Self, size: u31) !SubAllocation { self.used += size; var smallestBlock: ?*SubAllocation = null; for(self.freeBlocks.items, 0..) |*block, i| { if(size == block.len) { return self.freeBlocks.swapRemove(i); } if(size < block.len and if(smallestBlock) |_smallestBlock| block.len > _smallestBlock.len else true) { smallestBlock = block; } } if(smallestBlock) |block| { const result = SubAllocation {.start = block.start, .len = size}; block.start += size; block.len -= size; return result; } else { std.log.info("Resizing internal mesh buffer from {} MiB to {} MiB", .{self.capacity*@sizeOf(Entry) >> 20, (self.capacity*@sizeOf(Entry) >> 20)*2}); const oldBuffer = self.ssbo; defer oldBuffer.deinit(); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, oldBuffer.bufferID); _ = c.glUnmapBuffer(c.GL_SHADER_STORAGE_BUFFER); const oldCapacity = self.capacity; self.createBuffer(self.capacity*2); // TODO: Is there a way to free the old buffer before creating the new one? self.used += self.capacity - oldCapacity; try self.finalFree(.{.start = oldCapacity, .len = self.capacity - oldCapacity}); c.glBindBuffer(c.GL_COPY_READ_BUFFER, oldBuffer.bufferID); c.glBindBuffer(c.GL_COPY_WRITE_BUFFER, self.ssbo.bufferID); c.glCopyBufferSubData(c.GL_COPY_READ_BUFFER, c.GL_COPY_WRITE_BUFFER, 0, 0, @as(c_long, oldCapacity)*@sizeOf(Entry)); return alloc(self, size); } } fn finalFree(self: *Self, _allocation: SubAllocation) !void { self.used -= _allocation.len; var allocation = _allocation; if(allocation.len == 0) return; for(self.freeBlocks.items, 0..) |*block, i| { if(allocation.start + allocation.len == block.start) { allocation.len += block.len; _ = self.freeBlocks.swapRemove(i); break; } } for(self.freeBlocks.items) |*block| { if(allocation.start == block.start + block.len) { block.len += allocation.len; return; } } try self.freeBlocks.append(allocation); } pub fn free(self: *Self, allocation: SubAllocation) !void { if(allocation.len == 0) return; try self.fencedFreeLists[self.activeFence].append(allocation); } pub fn uploadData(self: *Self, data: []const Entry, allocation: *SubAllocation) !void { try self.free(allocation.*); if(data.len == 0) { allocation.len = 0; return; } allocation.* = try self.alloc(@intCast(data.len)); @memcpy(self.persistentBuffer[allocation.start..allocation.start].ptr, data); } }; } pub const FrameBuffer = struct { frameBuffer: c_uint, texture: c_uint, hasDepthTexture: bool, depthTexture: c_uint, pub fn init(self: *FrameBuffer, hasDepthTexture: bool, textureFilter: c_int, textureWrap: c_int) void { self.* = FrameBuffer{ .frameBuffer = undefined, .texture = undefined, .depthTexture = undefined, .hasDepthTexture = hasDepthTexture, }; c.glGenFramebuffers(1, &self.frameBuffer); c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.frameBuffer); if(hasDepthTexture) { c.glGenTextures(1, &self.depthTexture); c.glBindTexture(c.GL_TEXTURE_2D, self.depthTexture); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, textureFilter); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, textureFilter); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, textureWrap); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, textureWrap); c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_DEPTH_ATTACHMENT, c.GL_TEXTURE_2D, self.depthTexture, 0); } c.glGenTextures(1, &self.texture); c.glBindTexture(c.GL_TEXTURE_2D, self.texture); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, textureFilter); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, textureFilter); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, textureWrap); c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, textureWrap); c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT0, c.GL_TEXTURE_2D, self.texture, 0); c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); } pub fn deinit(self: *FrameBuffer) void { c.glDeleteFramebuffers(1, &self.frameBuffer); if(self.hasDepthTexture) { c.glDeleteRenderbuffers(1, &self.depthTexture); } c.glDeleteTextures(1, &self.texture); } pub fn updateSize(self: *FrameBuffer, _width: u31, _height: u31, internalFormat: c_int) void { const width = @max(_width, 1); const height = @max(_height, 1); c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.frameBuffer); if(self.hasDepthTexture) { c.glBindTexture(c.GL_TEXTURE_2D, self.depthTexture); c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_DEPTH_COMPONENT32F, width, height, 0, c.GL_DEPTH_COMPONENT, c.GL_FLOAT, null); } c.glBindTexture(c.GL_TEXTURE_2D, self.texture); c.glTexImage2D(c.GL_TEXTURE_2D, 0, internalFormat, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); } pub fn clear(_: FrameBuffer, clearColor: Vec4f) void { c.glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT); } pub fn validate(self: *const FrameBuffer) bool { c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.frameBuffer); defer c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); if(c.glCheckFramebufferStatus(c.GL_FRAMEBUFFER) != c.GL_FRAMEBUFFER_COMPLETE) { std.log.err("Frame Buffer Object error: {}", .{c.glCheckFramebufferStatus(c.GL_FRAMEBUFFER)}); return false; } return true; } pub fn bindTexture(self: *const FrameBuffer, target: c_uint) void { c.glActiveTexture(target); c.glBindTexture(c.GL_TEXTURE_2D, self.texture); } pub fn bindDepthTexture(self: *const FrameBuffer, target: c_uint) void { std.debug.assert(self.hasDepthTexture); c.glActiveTexture(target); c.glBindTexture(c.GL_TEXTURE_2D, self.depthTexture); } pub fn bind(self: *const FrameBuffer) void { c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.frameBuffer); } pub fn unbind(_: *const FrameBuffer) void { c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); } }; pub const TextureArray = struct { textureID: c_uint, pub fn init() TextureArray { var self: TextureArray = undefined; c.glGenTextures(1, &self.textureID); return self; } pub fn deinit(self: TextureArray) void { c.glDeleteTextures(1, &self.textureID); } pub fn bind(self: TextureArray) void { c.glBindTexture(c.GL_TEXTURE_2D_ARRAY, self.textureID); } fn lodColorInterpolation(colors: [4]Color) Color { var r: [4]f32 = undefined; var g: [4]f32 = undefined; var b: [4]f32 = undefined; var a: [4]f32 = undefined; for(0..4) |i| { r[i] = @floatFromInt(colors[i].r); g[i] = @floatFromInt(colors[i].g); b[i] = @floatFromInt(colors[i].b); a[i] = @floatFromInt(colors[i].a); } // Use gamma corrected average(https://stackoverflow.com/a/832314/13082649): var aSum: f32 = 0; var rSum: f32 = 0; var gSum: f32 = 0; var bSum: f32 = 0; for(0..4) |i| { const w = a[i]*a[i]; aSum += w; rSum += w*r[i]*r[i]; gSum += w*g[i]*g[i]; bSum += w*b[i]*b[i]; } aSum = @round(@sqrt(aSum))/2; rSum = @round(@sqrt(rSum))/2; gSum = @round(@sqrt(gSum))/2; bSum = @round(@sqrt(bSum))/2; if(aSum != 0) { rSum /= aSum; gSum /= aSum; bSum /= aSum; } return Color{.r=@intFromFloat(rSum), .g=@intFromFloat(gSum), .b=@intFromFloat(bSum), .a=@intFromFloat(aSum)}; } /// (Re-)Generates the GPU buffer. pub fn generate(self: TextureArray, images: []Image, mipmapping: bool) !void { var maxWidth: u31 = 0; var maxHeight: u31 = 0; for(images) |image| { maxWidth = @max(maxWidth, image.width); maxHeight = @max(maxHeight, image.height); } // Make sure the width and height use a power of 2: if(maxWidth-1 & maxWidth != 0) { maxWidth = @as(u31, 2) << std.math.log2_int(u31, maxWidth); } if(maxHeight-1 & maxHeight != 0) { maxHeight = @as(u31, 2) << std.math.log2_int(u31, maxHeight); } std.log.debug("Creating Texture Array of size {}×{} with {} layers.", .{maxWidth, maxHeight, images.len}); self.bind(); const maxLOD = if(mipmapping) 1 + std.math.log2_int(u31, @min(maxWidth, maxHeight)) else 1; c.glTexStorage3D(c.GL_TEXTURE_2D_ARRAY, maxLOD, c.GL_RGBA8, maxWidth, maxHeight, @intCast(images.len)); var arena = std.heap.ArenaAllocator.init(main.threadAllocator); defer arena.deinit(); const lodBuffer: [][]Color = try arena.allocator().alloc([]Color, maxLOD); for(lodBuffer, 0..) |*buffer, i| { buffer.* = try arena.allocator().alloc(Color, (maxWidth >> @intCast(i))*(maxHeight >> @intCast(i))); } for(images, 0..) |image, i| { // Fill the buffer using nearest sampling. Probably not the best solutions for all textures, but that's what happens when someone doesn't use power of 2 textures... for(0..maxWidth) |x| { for(0..maxHeight) |y| { const index = x + y*maxWidth; const imageIndex = (x*image.width)/maxWidth + image.width*(y*image.height)/maxHeight; lodBuffer[0][index] = image.imageData[imageIndex]; } } // Calculate the mipmap levels: for(lodBuffer, 0..) |_, _lod| { const lod: u5 = @intCast(_lod); const curWidth = maxWidth >> lod; const curHeight = maxHeight >> lod; if(lod != 0) { for(0..curWidth) |x| { for(0..curHeight) |y| { const index = x + y*curWidth; const index2 = 2*x + 2*y*2*curWidth; const colors = [4]Color { lodBuffer[lod-1][index2], lodBuffer[lod-1][index2 + 1], lodBuffer[lod-1][index2 + curWidth*2], lodBuffer[lod-1][index2 + curWidth*2 + 1], }; lodBuffer[lod][index] = lodColorInterpolation(colors); } } } c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, @intCast(i), curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, lodBuffer[lod].ptr); } } c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MAX_LOD, maxLOD); //glGenerateMipmap(GL_TEXTURE_2D_ARRAY); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST_MIPMAP_LINEAR); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT); } }; pub const Texture = struct { textureID: c_uint, pub fn init() Texture { var self: Texture = undefined; c.glGenTextures(1, &self.textureID); return self; } pub fn initFromFile(path: []const u8) !Texture { const self = Texture.init(); const image = try Image.readFromFile(main.threadAllocator, path); defer image.deinit(main.threadAllocator); self.generate(image); return self; } pub fn deinit(self: Texture) void { c.glDeleteTextures(1, &self.textureID); } pub fn bindTo(self: Texture, binding: u5) void { c.glActiveTexture(@intCast(c.GL_TEXTURE0 + binding)); c.glBindTexture(c.GL_TEXTURE_2D, self.textureID); } pub fn bind(self: Texture) void { c.glBindTexture(c.GL_TEXTURE_2D, self.textureID); } /// (Re-)Generates the GPU buffer. pub fn generate(self: Texture, image: Image) void { self.bind(); c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_RGBA8, image.width, image.height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, image.imageData.ptr); 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); } pub fn render(self: Texture, pos: Vec2f, dim: Vec2f) void { self.bindTo(0); draw.boundImage(pos, dim); } }; pub const CubeMapTexture = struct { textureID: c_uint, pub fn init() CubeMapTexture { var self: CubeMapTexture = undefined; c.glGenTextures(1, &self.textureID); return self; } pub fn deinit(self: CubeMapTexture) void { c.glDeleteTextures(1, &self.textureID); } pub fn bindTo(self: CubeMapTexture, binding: u5) void { c.glActiveTexture(@intCast(c.GL_TEXTURE0 + binding)); c.glBindTexture(c.GL_TEXTURE_CUBE_MAP, self.textureID); } pub fn bind(self: CubeMapTexture) void { c.glBindTexture(c.GL_TEXTURE_CUBE_MAP, self.textureID); } /// (Re-)Generates the GPU buffer. pub fn generate(self: CubeMapTexture, width: u31, height: u31) void { self.bind(); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexImage2D(c.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, c.GL_RGBA8, width, height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_MAG_FILTER, c.GL_LINEAR); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_WRAP_S, c.GL_CLAMP_TO_EDGE); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_WRAP_T, c.GL_CLAMP_TO_EDGE); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_WRAP_R, c.GL_CLAMP_TO_EDGE); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_BASE_LEVEL, 0); c.glTexParameteri(c.GL_TEXTURE_CUBE_MAP, c.GL_TEXTURE_MAX_LEVEL, 0); } pub fn faceNormal(face: usize) Vec3f { const normals = [_]Vec3f { .{ 1, 0, 0}, // +x .{-1, 0, 0}, // -x .{0, 1, 0}, // +y .{0, -1, 0}, // -y .{0, 0, 1}, // +z .{0, 0, -1}, // -z }; return normals[face]; } pub fn faceUp(face: usize) Vec3f { const ups = [_]Vec3f { .{0, -1, 0}, // +x .{0, -1, 0}, // -x .{0, 0, 1}, // +y .{0, 0, -1}, // -y .{0, -1, 0}, // +z .{0, -1, 0}, // -z }; return ups[face]; } pub fn faceRight(face: usize) Vec3f { comptime var rights: [6]Vec3f = undefined; inline for(0..6) |i| { rights[i] = comptime vec.cross(faceNormal(i), faceUp(i)); } return rights[face]; } pub fn bindToFramebuffer(self: CubeMapTexture, fb: FrameBuffer, face: c_uint) void { fb.bind(); c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT0, @as(c_uint, c.GL_TEXTURE_CUBE_MAP_POSITIVE_X) + face, self.textureID, 0); } }; pub const Color = extern struct { r: u8, g: u8, b: u8, a: u8, pub fn toARBG(self: Color) u32 { return @as(u32, self.a)<<24 | @as(u32, self.r)<<16 | @as(u32, self.g)<<8 | @as(u32, self.b); } }; pub const Image = struct { var defaultImageData = [4]Color { Color{.r=0, .g=0, .b=0, .a=255}, Color{.r=255, .g=0, .b=255, .a=255}, Color{.r=255, .g=0, .b=255, .a=255}, Color{.r=0, .g=0, .b=0, .a=255}, }; pub const defaultImage = Image { .width = 2, .height = 2, .imageData = &defaultImageData, }; var emptyImageData = [1]Color { Color{.r=0, .g=0, .b=0, .a=0}, }; pub const emptyImage = Image { .width = 1, .height = 1, .imageData = &emptyImageData, }; width: u31, height: u31, imageData: []Color, pub fn init(allocator: Allocator, width: u31, height: u31) !Image { return Image{ .width = width, .height = height, .imageData = try allocator.alloc(Color, width*height), }; } pub fn deinit(self: Image, allocator: Allocator) void { if(self.imageData.ptr == &defaultImageData) return; allocator.free(self.imageData); } pub fn readFromFile(allocator: Allocator, path: []const u8) !Image { var result: Image = undefined; var channel: c_int = undefined; const nullTerminatedPath = try std.fmt.allocPrintZ(main.threadAllocator, "{s}", .{path}); // TODO: Find a more zig-friendly image loading library. defer main.threadAllocator.free(nullTerminatedPath); stb_image.stbi_set_flip_vertically_on_load(1); const data = stb_image.stbi_load(nullTerminatedPath.ptr, @ptrCast(&result.width), @ptrCast(&result.height), &channel, 4) orelse { return error.FileNotFound; }; result.imageData = try allocator.dupe(Color, @as([*]Color, @ptrCast(data))[0..result.width*result.height]); stb_image.stbi_image_free(data); return result; } pub fn exportToFile(self: Image, path: []const u8) !void { const nullTerminated = try main.threadAllocator.dupeZ(u8, path); defer main.threadAllocator.free(nullTerminated); _ = stb_image.stbi_write_png(nullTerminated.ptr, self.width, self.height, 4, self.imageData.ptr, self.width*4); } pub fn getRGB(self: Image, x: usize, y: usize) Color { std.debug.assert(x < self.width); std.debug.assert(y < self.height); const index = x + y*self.width; return self.imageData[index]; } pub fn setRGB(self: Image, x: usize, y: usize, rgb: Color) void { std.debug.assert(x < self.width); std.debug.assert(y < self.height); const index = x + y*self.width; self.imageData[index] = rgb; } }; pub const Fog = struct { color: Vec3f, density: f32, }; const block_texture = struct { var uniforms: struct { color: c_int, transparent: c_int, } = undefined; var shader: Shader = undefined; var depthTexture: Texture = undefined; const textureSize = 128; fn init() !void { shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/item_texture_post.vs", "assets/cubyz/shaders/item_texture_post.fs", &uniforms); depthTexture = Texture.init(); depthTexture.bind(); var data: [128*128]f32 = undefined; const z: f32 = 134; const near = main.renderer.zNear; const far = main.renderer.zFar; const depth = ((far + near)/(near - far)*z + 2*near*far/(near - far))/-z*0.5 + 0.5; @memset(&data, depth); c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R32F, textureSize, textureSize, 0, c.GL_RED, c.GL_FLOAT, &data); 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 { shader.deinit(); depthTexture.deinit(); } }; pub fn generateBlockTexture(blockType: u16) !Texture { const block = main.blocks.Block{.typ = blockType, .data = 0}; // TODO: Use natural standard data. const textureSize = block_texture.textureSize; c.glViewport(0, 0, textureSize, textureSize); var frameBuffer: FrameBuffer = undefined; const scissor = c.glIsEnabled(c.GL_SCISSOR_TEST); c.glDisable(c.GL_SCISSOR_TEST); defer if(scissor != 0) c.glEnable(c.GL_SCISSOR_TEST); const depthTest = c.glIsEnabled(c.GL_DEPTH_TEST); c.glDisable(c.GL_DEPTH_TEST); defer if(depthTest != 0) c.glEnable(c.GL_DEPTH_TEST); const cullFace = c.glIsEnabled(c.GL_CULL_FACE); c.glDisable(c.GL_CULL_FACE); defer if(cullFace != 0) c.glEnable(c.GL_CULL_FACE); frameBuffer.init(false, c.GL_NEAREST, c.GL_REPEAT); defer frameBuffer.deinit(); frameBuffer.updateSize(textureSize, textureSize, c.GL_RGBA16F); frameBuffer.bind(); if(block.transparent()) { frameBuffer.clear(.{0.683421, 0.6854237, 0.685426, 1}); } else { frameBuffer.clear(.{0, 0, 0, 0}); } const projMatrix = Mat4f.perspective(0.013, 1, 64, 256); const oldViewMatrix = main.game.camera.viewMatrix; main.game.camera.viewMatrix = Mat4f.rotationX(std.math.pi/4.0).mul(Mat4f.rotationY(-std.math.pi/4.0)); defer main.game.camera.viewMatrix = oldViewMatrix; if(block.transparent()) { c.glBlendEquation(c.GL_FUNC_ADD); c.glBlendFunc(c.GL_ONE, c.GL_SRC1_COLOR); main.chunk.meshing.bindTransparentShaderAndUniforms(projMatrix, .{1, 1, 1}); } else { if(block.mode().model(block).modelIndex == 0) { main.chunk.meshing.bindShaderAndUniforms(projMatrix, .{1, 1, 1}); } else { main.chunk.meshing.bindVoxelShaderAndUniforms(projMatrix, .{1, 1, 1}); } } const uniforms = if(block.transparent()) &main.chunk.meshing.transparentUniforms else &main.chunk.meshing.uniforms; var faceData: [6]main.chunk.meshing.FaceData = undefined; var faces: u8 = 0; if(block.hasBackFace()) { faceData[2] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1, 1, 1, true); faceData[1] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1, 1, true); faceData[0] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1, true); faces += 3; } faceData[faces + 0] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosX, 1+1, 1, 1, false); faceData[faces + 1] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirUp, 1, 1+1, 1, false); faceData[faces + 2] = main.chunk.meshing.ChunkMesh.constructFaceData(block, main.chunk.Neighbors.dirPosZ, 1, 1, 1+1, false); faces += 3; // TODO: Lighting var allocation: SubAllocation = .{.start = 0, .len = 0}; try main.chunk.meshing.faceBuffer.uploadData(faceData[0..faces], &allocation); c.glUniform3f(uniforms.modelPosition, -65.5 - 1.5, -92.631 - 1.5, -65.5 - 1.5); c.glUniform1i(uniforms.visibilityMask, 0xff); c.glUniform1i(uniforms.voxelSize, 1); c.glActiveTexture(c.GL_TEXTURE0); main.blocks.meshes.blockTextureArray.bind(); c.glActiveTexture(c.GL_TEXTURE1); main.blocks.meshes.emissionTextureArray.bind(); block_texture.depthTexture.bindTo(3); c.glDrawElementsBaseVertex(c.GL_TRIANGLES, 6*faces, c.GL_UNSIGNED_INT, null, allocation.start*4); var finalFrameBuffer: FrameBuffer = undefined; finalFrameBuffer.init(false, c.GL_NEAREST, c.GL_REPEAT); finalFrameBuffer.updateSize(textureSize, textureSize, c.GL_RGBA8); finalFrameBuffer.bind(); const texture = Texture{.textureID = finalFrameBuffer.texture}; defer c.glDeleteFramebuffers(1, &finalFrameBuffer.frameBuffer); block_texture.shader.bind(); c.glUniform1i(block_texture.uniforms.transparent, if(block.transparent()) c.GL_TRUE else c.GL_FALSE); c.glUniform1i(block_texture.uniforms.color, 3); frameBuffer.bindTexture(c.GL_TEXTURE3); c.glBindVertexArray(draw.rectVAO); c.glDisable(c.GL_BLEND); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); c.glEnable(c.GL_BLEND); c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); try main.chunk.meshing.faceBuffer.free(allocation); c.glViewport(0, 0, main.Window.width, main.Window.height); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); return texture; }