Various performance improvements:

The NoiseCaveGenerator now produces all (noise + interpolateValue) values at once instead of recalculating them 7 times on average.

The TerrainGenerator no longer uses integer division.

The chunk meshing doesn't calculate the giant switch case in the middle of `cenBeSeenThroughOtherBlock()` if the block as the simple cube model.

And some smaller things.
This commit is contained in:
IntegratedQuantum 2023-05-10 21:25:24 +02:00
parent 83f4503390
commit f15d1adc56
8 changed files with 66 additions and 51 deletions

View File

@ -25,7 +25,7 @@ pub const BlockClass = enum(u8) {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
var allocator = arena.allocator();
pub const MaxBLockCount: usize = 65536; // 16 bit limit
pub const maxBlockCount: usize = 65536; // 16 bit limit
pub const BlockDrop = struct {
item: items.Item,
@ -56,26 +56,26 @@ pub const Ore = struct {
}
};
var _lightingTransparent: [MaxBLockCount]bool = undefined;
var _transparent: [MaxBLockCount]bool = undefined;
var _id: [MaxBLockCount][]u8 = undefined;
var _lightingTransparent: [maxBlockCount]bool = undefined;
var _transparent: [maxBlockCount]bool = undefined;
var _id: [maxBlockCount][]u8 = undefined;
/// Time in seconds to break this block by hand.
var _hardness: [MaxBLockCount]f32 = undefined;
var _hardness: [maxBlockCount]f32 = undefined;
/// Minimum pickaxe/axe/shovel power required.
var _breakingPower: [MaxBLockCount]f32 = undefined;
var _solid: [MaxBLockCount]bool = undefined;
var _selectable: [MaxBLockCount]bool = undefined;
var _blockDrops: [MaxBLockCount][]BlockDrop = undefined;
var _breakingPower: [maxBlockCount]f32 = undefined;
var _solid: [maxBlockCount]bool = undefined;
var _selectable: [maxBlockCount]bool = undefined;
var _blockDrops: [maxBlockCount][]BlockDrop = undefined;
/// Meaning undegradable parts of trees or other structures can grow through this block.
var _degradable: [MaxBLockCount]bool = undefined;
var _viewThrough: [MaxBLockCount]bool = undefined;
var _blockClass: [MaxBLockCount]BlockClass = undefined;
var _light: [MaxBLockCount]u32 = undefined;
var _degradable: [maxBlockCount]bool = undefined;
var _viewThrough: [maxBlockCount]bool = undefined;
var _blockClass: [maxBlockCount]BlockClass = undefined;
var _light: [maxBlockCount]u32 = undefined;
/// How much light this block absorbs if it is transparent
var _absorption: [MaxBLockCount]u32 = undefined;
var _absorption: [maxBlockCount]u32 = undefined;
/// GUI that is opened on click.
var _gui: [MaxBLockCount][]u8 = undefined;
var _mode: [MaxBLockCount]*RotationMode = undefined;
var _gui: [maxBlockCount][]u8 = undefined;
var _mode: [maxBlockCount]*RotationMode = undefined;
var reverseIndices = std.StringHashMap(u16).init(arena.allocator());
@ -310,10 +310,10 @@ pub const meshes = struct {
time: i32,
};
var size: u32 = 0;
var _modelIndex: [MaxBLockCount]u16 = undefined;
var _textureIndices: [MaxBLockCount][6]u32 = undefined;
var _modelIndex: [maxBlockCount]u16 = undefined;
var _textureIndices: [maxBlockCount][6]u32 = undefined;
/// Stores the number of textures after each block was added. Used to clean additional textures when the world is switched.
var maxTextureCount: [MaxBLockCount]u32 = undefined;
var maxTextureCount: [maxBlockCount]u32 = undefined;
/// Number of loaded meshes. Used to determine if an update is needed.
var loadedMeshes: u32 = 0;

View File

@ -488,7 +488,7 @@ pub const meshing = struct {
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
const rotatedModel = blocks.meshes.model(block);
const model = &models.voxelModels.items[rotatedModel.modelIndex];
const freestandingModel = switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) {
const freestandingModel = rotatedModel.modelIndex != models.fullCube and switch(rotatedModel.permutation.permuteNeighborIndex(neighbor)) {
Neighbors.dirNegX => model.min[0] != 0,
Neighbors.dirPosX => model.max[0] != 16,
Neighbors.dirDown => model.min[1] != 0,

View File

@ -208,6 +208,7 @@ pub fn getModelIndex(string: []const u8) u16 {
}
pub var voxelModels: std.ArrayList(VoxelModel) = undefined;
pub var fullCube: u16 = 0;
// TODO: Allow loading from world assets.
// TODO: Editable player models.
@ -220,6 +221,7 @@ pub fn init() !void {
nameToIndex = std.StringHashMap(u16).init(main.threadAllocator);
try nameToIndex.put("cube", @intCast(u16, voxelModels.items.len));
fullCube = @intCast(u16, voxelModels.items.len);
(try voxelModels.addOne()).init(cube);
try nameToIndex.put("log", @intCast(u16, voxelModels.items.len));

View File

@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const main = @import("root");
const Array3D = main.utils.Array3D;
const Cache = main.utils.Cache;
const Chunk = main.chunk.Chunk;
const ChunkPosition = main.chunk.ChunkPosition;
@ -25,8 +26,7 @@ pub const CaveBiomeMapFragment = struct {
pub const caveBiomeMapMask = caveBiomeMapSize - 1;
pos: main.chunk.ChunkPosition,
biomeMap0: [1 << 3*(caveBiomeMapShift - caveBiomeShift)]*const Biome = undefined,
biomeMap1: [1 << 3*(caveBiomeMapShift - caveBiomeShift)]*const Biome = undefined,
biomeMap: [1 << 3*(caveBiomeMapShift - caveBiomeShift)][2]*const Biome = undefined,
refCount: std.atomic.Atomic(u16) = std.atomic.Atomic(u16).init(0),
pub fn init(self: *CaveBiomeMapFragment, wx: i32, wy: i32, wz: i32) !void {
@ -178,6 +178,24 @@ pub const InterpolatableCaveBiomeMapView = struct {
return @select(i32, in >= Vec3i{0, 0, 0}, Vec3i{1, 1, 1}, Vec3i{-1, -1, -1});
}
pub fn bulkInterpolateValue(self: InterpolatableCaveBiomeMapView, comptime field: []const u8, wx: i32, wy: i32, wz: i32, voxelSize: u31, map: Array3D(f32), comptime mode: enum{addToMap}, comptime scale: f32) void {
var x: u31 = 0;
while(x < map.width) : (x += 1) {
var y: u31 = 0;
while(y < map.height) : (y += 1) {
var z: u31 = 0;
while(z < map.depth) : (z += 1) {
switch (mode) {
.addToMap => {
// TODO: Do a tetrahedron voxelization here, so parts of the tetrahedral barycentric coordinates can be precomputed.
map.ptr(x, y, z).* += scale*interpolateValue(self, wx + x*voxelSize, wy + y*voxelSize, wz + z*voxelSize, field);
}
}
}
}
}
}
pub noinline fn interpolateValue(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, comptime field: []const u8) f32 {
const worldPos = Vec3i{wx, wy, wz};
const closestGridpoint0 = (worldPos + @splat(3, @as(i32, CaveBiomeMapFragment.caveBiomeSize/2))) & @splat(3, ~@as(i32, CaveBiomeMapFragment.caveBiomeMask));
@ -283,7 +301,7 @@ pub const InterpolatableCaveBiomeMapView = struct {
return self.surfaceFragments[index].getBiome(wx, wz);
}
fn _getBiome(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, map: u1) *const Biome {
noinline fn _getBiome(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, map: u1) *const Biome {
var index: u8 = 0;
if(wx - self.fragments[0].pos.wx >= CaveBiomeMapFragment.caveBiomeMapSize) {
index += 4;
@ -298,15 +316,11 @@ pub const InterpolatableCaveBiomeMapView = struct {
const relY = @intCast(u31, wy - self.fragments[index].pos.wy);
const relZ = @intCast(u31, wz - self.fragments[index].pos.wz);
const indexInArray = CaveBiomeMapFragment.getIndex(relX, relY, relZ);
if(map == 0) {
return self.fragments[index].biomeMap0[indexInArray];
} else {
return self.fragments[index].biomeMap1[indexInArray];
}
return self.fragments[index].biomeMap[indexInArray][map];
}
/// Useful when the rough biome location is enough, for example for music.
pub fn getRoughBiome(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, nullSeed: ?*u64, comptime _checkSurfaceBiome: bool) *const Biome {
pub noinline fn getRoughBiome(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, comptime getSeed: bool, seed: *u64, comptime _checkSurfaceBiome: bool) *const Biome {
if(_checkSurfaceBiome) {
if(self.checkSurfaceBiome(wx, wy, wz)) |surfaceBiome| {
return surfaceBiome;
@ -328,7 +342,7 @@ pub const InterpolatableCaveBiomeMapView = struct {
map = 1;
}
if(nullSeed) |seed| {
if(getSeed) {
// A good old "I don't know what I'm doing" hash:
seed.* = @bitCast(u64, @as(i64, gridPointX) << 48 ^ @as(i64, gridPointY) << 23 ^ @as(i64, gridPointZ) << 11 ^ @as(i64, gridPointX) >> 5 ^ @as(i64, gridPointY) << 3 ^ @as(i64, gridPointZ) ^ @as(i64, map)*5427642781) ^ main.server.world.?.seed;
}
@ -383,11 +397,11 @@ pub const CaveBiomeMapView = struct {
}
pub fn getBiome(self: CaveBiomeMapView, relX: i32, relY: i32, relZ: i32) *const Biome {
return self.getBiomeAndSeed(relX, relY, relZ, null);
return self.getBiomeAndSeed(relX, relY, relZ, false, undefined);
}
/// Also returns a seed that is unique for the corresponding biome position.
pub fn getBiomeAndSeed(self: CaveBiomeMapView, relX: i32, relY: i32, relZ: i32, seed: ?*u64) *const Biome {
pub noinline fn getBiomeAndSeed(self: CaveBiomeMapView, relX: i32, relY: i32, relZ: i32, comptime getSeed: bool, seed: *u64) *const Biome {
std.debug.assert(relX >= -32 and relX < self.super.width + 32); // coordinate out of bounds
std.debug.assert(relY >= -32 and relY < self.super.width + 32); // coordinate out of bounds
std.debug.assert(relZ >= -32 and relZ < self.super.width + 32); // coordinate out of bounds
@ -407,7 +421,7 @@ pub const CaveBiomeMapView = struct {
wz += @floatToInt(i32, valueZ);
};
return self.super.getRoughBiome(wx, wy, wz, seed, false);
return self.super.getRoughBiome(wx, wy, wz, getSeed, seed, false);
}
};

View File

@ -83,11 +83,7 @@ pub fn generate(map: *CaveBiomeMapFragment, worldSeed: u64) Allocator.Error!void
if(randomValue < 0) break;
}
var index = CaveBiomeMapFragment.getIndex(x, y, z);
if(_map == 0) {
map.biomeMap0[index] = biome;
} else {
map.biomeMap1[index] = biome;
}
map.biomeMap[index][_map] = biome;
}
}
}

View File

@ -38,8 +38,9 @@ pub fn deinit() void {
const scale = 64;
const interpolatedPart = 4;
fn getValue(noise: Array3D(f32), map: *CaveMapFragment, biomeMap: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32) f32 {
return noise.get(@intCast(u31, wx - map.pos.wx) >> map.voxelShift, @intCast(u31, wy - map.pos.wy) >> map.voxelShift, @intCast(u31, wz - map.pos.wz) >> map.voxelShift) + biomeMap.interpolateValue(wx, wy, wz, "caves")*scale;
fn getValue(noise: Array3D(f32), map: *CaveMapFragment, outerSize: u31, biomeMap: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32) f32 {
_ = biomeMap; // TODO: clean this up at some point.
return noise.get(@intCast(u31, wx - map.pos.wx)/outerSize, @intCast(u31, wy - map.pos.wy)/outerSize, @intCast(u31, wz - map.pos.wz)/outerSize);// + biomeMap.interpolateValue(wx, wy, wz, "caves")*scale;
}
pub fn generate(map: *CaveMapFragment, worldSeed: u64) Allocator.Error!void {
@ -47,22 +48,23 @@ pub fn generate(map: *CaveMapFragment, worldSeed: u64) Allocator.Error!void {
const biomeMap = try InterpolatableCaveBiomeMapView.init(map.pos, CaveMapFragment.width*map.pos.voxelSize);
defer biomeMap.deinit();
const outerSize = @max(map.pos.voxelSize, interpolatedPart);
var noise = try FractalNoise3D.generateAligned(main.threadAllocator, map.pos.wx, map.pos.wy, map.pos.wz, map.pos.voxelSize, CaveMapFragment.width + 1, CaveMapFragment.height + 1, CaveMapFragment.width + 1, worldSeed, scale);//try Cached3DFractalNoise.init(map.pos.wx, map.pos.wy & ~@as(i32, CaveMapFragment.width*map.pos.voxelSize - 1), map.pos.wz, outerSize, map.pos.voxelSize*CaveMapFragment.width, worldSeed, scale);
var noise = try FractalNoise3D.generateAligned(main.threadAllocator, map.pos.wx, map.pos.wy, map.pos.wz, outerSize, CaveMapFragment.width*map.pos.voxelSize/outerSize + 1, CaveMapFragment.height*map.pos.voxelSize/outerSize + 1, CaveMapFragment.width*map.pos.voxelSize/outerSize + 1, worldSeed, scale);//try Cached3DFractalNoise.init(map.pos.wx, map.pos.wy & ~@as(i32, CaveMapFragment.width*map.pos.voxelSize - 1), map.pos.wz, outerSize, map.pos.voxelSize*CaveMapFragment.width, worldSeed, scale);
defer noise.deinit(main.threadAllocator);
biomeMap.bulkInterpolateValue("caves", map.pos.wx, map.pos.wy, map.pos.wz, outerSize, noise, .addToMap, scale);
var x: u31 = 0;
while(x < map.pos.voxelSize*CaveMapFragment.width) : (x += outerSize) {
var y: u31 = 0;
while(y < map.pos.voxelSize*CaveMapFragment.height) : (y += outerSize) {
var z: u31 = 0;
while(z < map.pos.voxelSize*CaveMapFragment.width) : (z += outerSize) {
const val000 = getValue(noise, map, biomeMap, x + map.pos.wx, y + map.pos.wy, z + map.pos.wz);
const val001 = getValue(noise, map, biomeMap, x + map.pos.wx, y + map.pos.wy, z + map.pos.wz + outerSize);
const val010 = getValue(noise, map, biomeMap, x + map.pos.wx, y + map.pos.wy + outerSize, z + map.pos.wz);
const val011 = getValue(noise, map, biomeMap, x + map.pos.wx, y + map.pos.wy + outerSize, z + map.pos.wz + outerSize);
const val100 = getValue(noise, map, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy, z + map.pos.wz);
const val101 = getValue(noise, map, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy, z + map.pos.wz + outerSize);
const val110 = getValue(noise, map, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy + outerSize, z + map.pos.wz);
const val111 = getValue(noise, map, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy + outerSize, z + map.pos.wz + outerSize);
const val000 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx, y + map.pos.wy, z + map.pos.wz);
const val001 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx, y + map.pos.wy, z + map.pos.wz + outerSize);
const val010 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx, y + map.pos.wy + outerSize, z + map.pos.wz);
const val011 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx, y + map.pos.wy + outerSize, z + map.pos.wz + outerSize);
const val100 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy, z + map.pos.wz);
const val101 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy, z + map.pos.wz + outerSize);
const val110 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy + outerSize, z + map.pos.wz);
const val111 = getValue(noise, map, outerSize, biomeMap, x + map.pos.wx + outerSize, y + map.pos.wy + outerSize, z + map.pos.wz + outerSize);
// Test if they are all inside or all outside the cave to skip these cases:
const measureForEquality = sign(val000) + sign(val001) + sign(val010) + sign(val011) + sign(val100) + sign(val101) + sign(val110) + sign(val111);
if(measureForEquality == -8) {

View File

@ -129,7 +129,7 @@ fn considerCrystal(x: i32, y: i32, z: i32, chunk: *main.chunk.Chunk, seed: *u64,
fn considerCoordinates(x: i32, y: i32, z: i32, chunk: *main.chunk.Chunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView, seed: *u64) void {
var oldSeed = seed.*;
const crystalSpawns = biomeMap.getBiomeAndSeed(x + main.chunk.chunkSize/2 - chunk.pos.wx, y + main.chunk.chunkSize/2 - chunk.pos.wy, z + main.chunk.chunkSize/2 - chunk.pos.wz, seed).crystals;
const crystalSpawns = biomeMap.getBiomeAndSeed(x + main.chunk.chunkSize/2 - chunk.pos.wx, y + main.chunk.chunkSize/2 - chunk.pos.wy, z + main.chunk.chunkSize/2 - chunk.pos.wz, true, seed).crystals;
random.scrambleSeed(seed);
var differendColors: u32 = 1;
if(random.nextInt(u1, seed) != 0) {

View File

@ -38,6 +38,7 @@ pub fn deinit() void {
}
pub fn generate(worldSeed: u64, chunk: *main.chunk.Chunk, caveMap: CaveMap.CaveMapView, biomeMap: CaveBiomeMap.CaveBiomeMapView) Allocator.Error!void {
const voxelSizeShift = @ctz(chunk.pos.voxelSize);
var x: u31 = 0;
while(x < chunk.width) : (x += chunk.pos.voxelSize) {
var z: u31 = 0;
@ -46,7 +47,7 @@ pub fn generate(worldSeed: u64, chunk: *main.chunk.Chunk, caveMap: CaveMap.CaveM
var makeSurfaceStructure = true;
var y: i32 = chunk.width - chunk.pos.voxelSize;
while(y >= 0) : (y -= chunk.pos.voxelSize) {
const mask = @as(u64, 1) << @intCast(u6, @divExact(y, chunk.pos.voxelSize));
const mask = @as(u64, 1) << @intCast(u6, y >> voxelSizeShift);
if(heightData & mask != 0) {
const biome = biomeMap.getBiome(x, y, z);