Skip to content

Commit

Permalink
Improve CPU-side light interpolation code, reducing the number of sam…
Browse files Browse the repository at this point in the history
…ples needed in the average case of axis aligned blocks.

Fixes #296
Helps with #277
  • Loading branch information
IntegratedQuantum committed Apr 25, 2024
1 parent ad1e79a commit d450fa5
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 15 deletions.
37 changes: 36 additions & 1 deletion src/models.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ pub const QuadInfo = extern struct {
textureSlot: u32,
};

const ExtraQuadInfo = struct {
faceNeighbor: ?u3,
isFullQuad: bool,
hasOnlyCornerVertices: bool,
alignedNormalDirection: ?u3,
};

const gridSize = 4096;

fn snapToGrid(x: anytype) @TypeOf(x) {
Expand Down Expand Up @@ -200,18 +207,44 @@ pub fn getModelIndex(string: []const u8) u16 {
}

pub var quads: main.List(QuadInfo) = undefined;
pub var extraQuadInfos: main.List(ExtraQuadInfo) = undefined;
pub var models: main.List(Model) = undefined;
pub var fullCube: u16 = undefined;

var quadDeduplication: std.AutoHashMap([@sizeOf(QuadInfo)]u8, u16) = undefined;

fn addQuad(info: QuadInfo) u16 { // TODO: Merge duplicates
fn addQuad(info: QuadInfo) u16 {
if(quadDeduplication.get(std.mem.toBytes(info))) |id| {
return id;
}
const index: u16 = @intCast(quads.items.len);
quads.append(info);
quadDeduplication.put(std.mem.toBytes(info), index) catch unreachable;

var extraQuadInfo: ExtraQuadInfo = undefined;
extraQuadInfo.faceNeighbor = Model.getFaceNeighbor(&info);
extraQuadInfo.isFullQuad = Model.fullyOccludesNeighbor(&info);
{
var zeroes: @Vector(3, u32) = .{0, 0, 0};
var ones: @Vector(3, u32) = .{0, 0, 0};
for(info.corners) |corner| {
zeroes += @select(u32, corner == @as(Vec3f, @splat(0)), .{1, 1, 1}, .{0, 0, 0});
ones += @select(u32, corner == @as(Vec3f, @splat(1)), .{1, 1, 1}, .{0, 0, 0});
}
const cornerValues = @reduce(.Add, zeroes) + @reduce(.Add, ones);
extraQuadInfo.hasOnlyCornerVertices = cornerValues == 4*3;
}
{
extraQuadInfo.alignedNormalDirection = null;
if(@reduce(.And, info.normal == Vec3f{-1, 0, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirNegX;
if(@reduce(.And, info.normal == Vec3f{1, 0, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirPosX;
if(@reduce(.And, info.normal == Vec3f{0, -1, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirNegY;
if(@reduce(.And, info.normal == Vec3f{0, 1, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirPosY;
if(@reduce(.And, info.normal == Vec3f{0, 0, -1})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirDown;
if(@reduce(.And, info.normal == Vec3f{0, 0, 1})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirUp;
}
extraQuadInfos.append(extraQuadInfo);

return index;
}

Expand Down Expand Up @@ -278,6 +311,7 @@ fn openBox(min: Vec3f, max: Vec3f, uvOffset: Vec2f, openSide: enum{x, y, z}) [4]
pub fn init() void {
models = main.List(Model).init(main.globalAllocator);
quads = main.List(QuadInfo).init(main.globalAllocator);
extraQuadInfos = main.List(ExtraQuadInfo).init(main.globalAllocator);
quadDeduplication = std.AutoHashMap([@sizeOf(QuadInfo)]u8, u16).init(main.globalAllocator.allocator);

nameToIndex = std.StringHashMap(u16).init(main.globalAllocator.allocator);
Expand Down Expand Up @@ -361,5 +395,6 @@ pub fn deinit() void {
}
models.deinit();
quads.deinit();
extraQuadInfos.deinit();
quadDeduplication.deinit();
}
71 changes: 57 additions & 14 deletions src/renderer/chunk_meshing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,61 @@ const PrimitiveMesh = struct {
return result;
}

fn getCornerLightAligned(parent: *ChunkMesh, pos: Vec3i, direction: u3) [6]u8 { // Fast path for algined normals, leading to 4 instead of 8 light samples.
const normal: Vec3f = @floatFromInt(Vec3i{chunk.Neighbors.relX[direction], chunk.Neighbors.relY[direction], chunk.Neighbors.relZ[direction]});
const lightPos = @as(Vec3f, @floatFromInt(pos)) + normal*@as(Vec3f, @splat(0.5)) - @as(Vec3f, @splat(0.5));
const startPos: Vec3i = @intFromFloat(@floor(lightPos));
var val: [6]f32 = .{0, 0, 0, 0, 0, 0};
var dx: i32 = 0;
while(dx <= 1): (dx += 1) {
var dy: i32 = 0;
while(dy <= 1): (dy += 1) {
const weight: f32 = 1.0/4.0;
const finalPos = startPos +% @as(Vec3i, @intCast(@abs(chunk.Neighbors.textureX[direction])))*@as(Vec3i, @splat(dx)) +% @as(Vec3i, @intCast(@abs(chunk.Neighbors.textureY[direction]*@as(Vec3i, @splat(dy)))));
const lightVal: [6]u8 = getLightAt(parent, finalPos[0], finalPos[1], finalPos[2]);
for(0..6) |i| {
val[i] += @as(f32, @floatFromInt(lightVal[i]))*weight;
}
}
}
var result: [6]u8 = undefined;
for(0..6) |i| {
result[i] = std.math.lossyCast(u8, val[i]);
}
return result;
}

fn packLightValues(rawVals: [4][6]u5) [4]u32 {
var result: [4]u32 = undefined;
for(0..4) |i| {
result[i] = (
@as(u32, rawVals[i][0]) << 25 |
@as(u32, rawVals[i][1]) << 20 |
@as(u32, rawVals[i][2]) << 15 |
@as(u32, rawVals[i][3]) << 10 |
@as(u32, rawVals[i][4]) << 5 |
@as(u32, rawVals[i][5]) << 0
);
}
return result;
}

fn getLight(parent: *ChunkMesh, blockPos: Vec3i, quadIndex: u16) [4]u32 {
// TODO: This is doing 12 interpolations of 8 values each. For full cube models only 4 interpolations or 4 values each would be needed.
const normal = models.quads.items[quadIndex].normal;
if(models.extraQuadInfos.items[quadIndex].hasOnlyCornerVertices) { // Fast path for simple quads.
var rawVals: [4][6]u5 = undefined;
for(0..4) |i| {
const vertexPos = models.quads.items[quadIndex].corners[i];
const fullPos = blockPos +% @as(Vec3i, @intFromFloat(vertexPos));
const fullValues = if(models.extraQuadInfos.items[quadIndex].alignedNormalDirection) |dir|
getCornerLightAligned(parent, fullPos, dir)
else getCornerLight(parent, fullPos, normal);
for(0..6) |j| {
rawVals[i][j] = std.math.lossyCast(u5, fullValues[j]/8);
}
}
return packLightValues(rawVals);
}
var cornerVals: [2][2][2][6]u8 = undefined;
{
var dx: u31 = 0;
Expand All @@ -314,7 +366,9 @@ const PrimitiveMesh = struct {
while(dy <= 1) : (dy += 1) {
var dz: u31 = 0;
while(dz <= 1) : (dz += 1) {
cornerVals[dx][dy][dz] = getCornerLight(parent, blockPos +% Vec3i{dx, dy, dz}, normal);
cornerVals[dx][dy][dz] = if(models.extraQuadInfos.items[quadIndex].alignedNormalDirection) |dir|
getCornerLightAligned(parent, blockPos +% Vec3i{dx, dy, dz}, dir)
else getCornerLight(parent, blockPos +% Vec3i{dx, dy, dz}, normal);
}
}
}
Expand Down Expand Up @@ -346,18 +400,7 @@ const PrimitiveMesh = struct {
rawVals[i][j] = std.math.lossyCast(u5, val[j]/8);
}
}
var result: [4]u32 = undefined;
for(0..4) |i| {
result[i] = (
@as(u32, rawVals[i][0]) << 25 |
@as(u32, rawVals[i][1]) << 20 |
@as(u32, rawVals[i][2]) << 15 |
@as(u32, rawVals[i][3]) << 10 |
@as(u32, rawVals[i][4]) << 5 |
@as(u32, rawVals[i][5]) << 0
);
}
return result;
return packLightValues(rawVals);
}

fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) void {
Expand Down

0 comments on commit d450fa5

Please sign in to comment.