Skip to content

Commit 2c0fe96

Browse files
committed
feat(perf-rework): DFC Phase 5 - continents full-tree e2e verified (2.7e-7)
- dfc_continents_e2e.cpp: continents (flat_cache + shifted_noise + shift + NormalNoise x2) GPU vs CPU double, real seed, far coords - result: maxDiff=2.7e-7 avgDiff=8.4e-8 (block-safe) - fix 3 bugs found by e2e: lacunarity 2^(firstOctave) not 2^(-firstOctave) (512x coord blowup, sign flip); maintainPrecision floor->trunc ((long) toward-zero, neg 2^25 off); normal noise dedup key (offset reused by shift_x/shift_z)
1 parent e594709 commit 2c0fe96

6 files changed

Lines changed: 312 additions & 10 deletions

File tree

.investigations/perf-rework/dfc-design.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,28 @@ NormalNoise double拆分+float vs 纯double(远坐标): maxDiff=3.559e-07 av
7777
- NormalNoise 误差 ~3.6e-7(double 坐标拆分 + float 采样),方块零影响,与单 octave 拆分(1.6e-7)/多 octave(1.4e-7)一致。
7878
- 踩坑:Python 诊断脚本 float `fade` 漏了 v(v² 写成 v³ 的少一个 v),shader 的 `perlinFadeF` 是对的。
7979

80-
## 七、未完成(Phase 5)
80+
## 七、Phase 5 进展(完整 DF 树端到端验证 ✅ continents
8181

82-
- 完整 DF 树(factor/depth)的 Vulkan 端到端验证(当前 base_3d_noise 已验证,NormalNoise 用 Python 验证,未上 GPU)。
83-
- shader 尺寸优化:sloped_cheese 1.6MB SPIR-V。
82+
```
83+
[device] NVIDIA GeForce RTX 4060 Laptop GPU
84+
[result] N=1024, continents DFC shader vs CPU double: maxDiff=2.699e-07 avgDiff=8.380e-08
85+
```
86+
87+
- **continents 完整链路**(flat_cache + shifted_noise + shift_a/shift_b + NormalNoise×2)GPU vs CPU 误差 2.7e-7,方块零影响。
88+
- 端到端数据流:seed → randomDeriver → split(noise key) → DoublePerlinNoiseSampler(modern 构造)→ 收集 perm/origin(octBase 布局)→ 上传 → GPU 采样 vs CPU。
89+
90+
### Phase 5 修的 3 个 bug
91+
92+
1. **lacunarity 公式反了**`2^(-firstOctave)``2^(firstOctave)`(noise.h 是 `2^(-j), j=-firstOctave`)——错时坐标放大 512 倍,噪声符号都错(误差 0.92)。
93+
2. **maintainPrecision floor→trunc**:noise.h 用 `(long)` 向零截断(trunc),shader 用了 floor(向下取整),负数差 2^25。
94+
3. **normal 噪声去重失效**:去重 key 用自增 `n{len}`,改成 noise key(offset 被 shift_x/shift_z 引用应复用)。
95+
96+
## 八、未完成(Phase 6)
97+
98+
- factor/depth(含 old_blended + spline + 多噪声)的端到端验证。
99+
- shader 尺寸优化。
84100

85-
## 、踩坑(Phase 1-4 保留)
101+
## 、踩坑(Phase 1-5 保留)
86102

87103
1. GLSL 保留字 `out` 不能作 buffer 变量名 → `outBuf`
88104
2. GLSL 的 C 风格类型转换 `(double)x` 在 fp64 下需 `GL_NV_explicit_typecast` → 用构造函数式 `double(x)`

.investigations/perf-rework/dfc_gen.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ def gen(self, df):
104104
return f"(float(interp_noise_{idx}({CX}, {CY}, {CZ})))"
105105
if t == "minecraft:noise":
106106
np = self._resolve_noise_params(df.get("noise", ""))
107-
idx = self._register_noise("normal", f"n{len(self.noise_instances)}", {
107+
idx = self._register_noise("normal", df.get("noise", ""), {
108108
"noise": df.get("noise", ""), "xz_scale": df.get("xz_scale", 1.0), "y_scale": df.get("y_scale", 1.0),
109109
"firstOctave": np["firstOctave"], "amplitudes": np["amplitudes"],
110110
})
111111
xz = df.get("xz_scale", 1.0); y = df.get("y_scale", 1.0)
112112
return f"normal_noise_{idx}(double({CX}) * {xz:.17g}, double({CY}) * {y:.17g}, double({CZ}) * {xz:.17g})"
113113
if t == "minecraft:shifted_noise":
114114
np = self._resolve_noise_params(df.get("noise", ""))
115-
idx = self._register_noise("normal", f"n{len(self.noise_instances)}", {
115+
idx = self._register_noise("normal", df.get("noise", ""), {
116116
"noise": df.get("noise", ""), "xz_scale": df.get("xz_scale", 1.0), "y_scale": df.get("y_scale", 1.0),
117117
"firstOctave": np["firstOctave"], "amplitudes": np["amplitudes"],
118118
})
@@ -122,7 +122,7 @@ def gen(self, df):
122122
return f"normal_noise_{idx}(double({CX}) * {xz:.17g} + double({sx}), double({CY}) * {y:.17g} + double({sy}), double({CZ}) * {xz:.17g} + double({sz}))"
123123
if t in ("minecraft:shift_a", "minecraft:shift_b", "minecraft:shift"):
124124
np = self._resolve_noise_params("minecraft:offset")
125-
idx = self._register_noise("normal", f"n{len(self.noise_instances)}", {
125+
idx = self._register_noise("normal", "minecraft:offset", {
126126
"noise": "minecraft:offset", "firstOctave": np["firstOctave"], "amplitudes": np["amplitudes"],
127127
})
128128
# 对齐 ShiftDF.sample:SHIFT_A y=0;SHIFT_B x=z,y=x,z=0;SHIFT 不变;×0.25×4
@@ -252,7 +252,7 @@ def _normal_func(self, idx, p, octBase):
252252
amps = p.get("amplitudes", [1.0])
253253
firstOctave = p.get("firstOctave", 0)
254254
n = len(amps)
255-
lacunarity = 2.0 ** (-firstOctave)
255+
lacunarity = 2.0 ** firstOctave # 2^(firstOctave)(对齐 noise.h 的 2^(-j), j=-firstOctave)
256256
persistence = (2.0 ** (n - 1)) / (2.0 ** n - 1.0)
257257
nonz = [i for i, a in enumerate(amps) if a != 0.0]
258258
j = min(nonz) if nonz else 0
@@ -322,7 +322,7 @@ def _shader_template(self, expr, funcs):
322322
{{ 0, 1, 1}}, {{ 0, -1, 1}}, {{ 0, 1, -1}}, {{ 0, -1, -1}},
323323
{{ 1, 1, 0}}, {{ 0, -1, 1}}, {{-1, 1, 0}}, {{ 0, -1, -1}}
324324
}};
325-
double maintainPrecision(double v) {{ return v - floor(v / 3.3554432E7 + 0.5) * 3.3554432E7; }}
325+
double maintainPrecision(double v) {{ return v - trunc(v / 3.3554432E7 + 0.5) * 3.3554432E7; }}
326326
double perlinFadeD(double v) {{ return v * v * v * (v * (v * 6.0 - 15.0) + 10.0); }}
327327
double lerpD(double d, double s, double e) {{ return s + d * (e - s); }}
328328
int mapPermD(int octBase, int v) {{ return int(permBuf.perm[octBase * 256 + uint(v & 255)]); }}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import json, dfc_gen, sys
2+
dfdir = r'E:\PYTHON\CoreSwap\versions\1.20.1\data\worldgen\data\minecraft\worldgen\density_function'
3+
ndir = r'E:\PYTHON\CoreSwap\versions\1.20.1\data\worldgen\data\minecraft\worldgen\noise'
4+
name = sys.argv[1]
5+
g = dfc_gen.DfcGen(dfdir, ndir)
6+
df = json.load(open(f'{dfdir}\\overworld\\{name}.json'))
7+
g.gen(df)
8+
octBase = 0
9+
for i, (kind, p) in enumerate(g.noise_instances):
10+
n = 40 if kind == 'old_blended' else 2 * len(p.get('amplitudes', [1.0]))
11+
print(f'[{i}] kind={kind} octBase={octBase} nOct={n} noise={p.get("noise","")} fo={p.get("firstOctave","")} amps={p.get("amplitudes","")}')
12+
octBase += n

.investigations/perf-rework/normalnoise_probe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def octave_sample_f32(pns, x, y, z, lacunarity, persistence, amps):
8080
amps = [1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0]
8181
firstOctave = -9
8282
n = len(amps)
83-
lacunarity = 2.0 ** (-firstOctave) # 512
83+
lacunarity = 2.0 ** firstOctave # 2^(firstOctave) = 2^-9 = 1/512(对齐 noise.h)
8484
persistence = (2.0**(n-1)) / (2.0**n - 1.0) # 256/511
8585
nonz = [i for i,a in enumerate(amps) if a != 0.0]
8686
j, k = min(nonz), max(nonz)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// dfc_continents_e2e.cpp —— continents 完整链路端到端验证(NormalNoise + shift + shifted_noise)
2+
#include <vulkan/vulkan.h>
3+
#include <cstdio>
4+
#include <cstdlib>
5+
#include <cstring>
6+
#include <vector>
7+
#include <fstream>
8+
#include <cmath>
9+
#include "noise.h"
10+
#include "xoroshiro.h"
11+
#include "md5.h"
12+
13+
#define CHECK_VK(fn) do { VkResult _r = (fn); if (_r != VK_SUCCESS) { \
14+
std::fprintf(stderr, "VK error %d at %s:%d (%s)\n", _r, __FILE__, __LINE__, #fn); \
15+
std::exit(1); } } while (0)
16+
17+
static std::vector<uint32_t> loadSpv(const char* path) {
18+
std::ifstream f(path, std::ios::binary | std::ios::ate);
19+
if (!f) { std::fprintf(stderr, "cannot open %s\n", path); std::exit(1); }
20+
std::streamsize n = f.tellg(); f.seekg(0);
21+
std::vector<uint32_t> code((size_t)n / 4); f.read((char*)code.data(), n);
22+
return code;
23+
}
24+
25+
// 收集一个 DoublePerlinNoiseSampler 的 perm/origin(first + second)
26+
static void collectNormal(const wg::DoublePerlinNoiseSampler& dn, int octBase,
27+
std::vector<uint32_t>& perm, std::vector<double>& origin) {
28+
int n = (int)dn.firstSampler.octaveSamplers.size();
29+
for (int i = 0; i < n; i++) {
30+
const wg::PerlinNoiseSampler* pn = dn.firstSampler.octaveSamplers[i].get();
31+
if (pn) {
32+
for (int k = 0; k < 256; k++) perm[(octBase + i) * 256 + k] = (uint32_t)pn->permutation[k];
33+
origin[(octBase + i) * 3 + 0] = pn->originX;
34+
origin[(octBase + i) * 3 + 1] = pn->originY;
35+
origin[(octBase + i) * 3 + 2] = pn->originZ;
36+
}
37+
pn = dn.secondSampler.octaveSamplers[i].get();
38+
if (pn) {
39+
for (int k = 0; k < 256; k++) perm[(octBase + n + i) * 256 + k] = (uint32_t)pn->permutation[k];
40+
origin[(octBase + n + i) * 3 + 0] = pn->originX;
41+
origin[(octBase + n + i) * 3 + 1] = pn->originY;
42+
origin[(octBase + n + i) * 3 + 2] = pn->originZ;
43+
}
44+
}
45+
}
46+
47+
int main() {
48+
const uint64_t worldSeed = 8576294172403134396ULL;
49+
wg::XoroshiroRandom base(worldSeed);
50+
auto randomDeriver = base.nextSplitter();
51+
52+
// continentalness(NormalNoise,fo=-9)+ offset(fo=-3)
53+
wg::DoublePerlinNoiseSampler continentalness(randomDeriver.split("minecraft:continentalness"),
54+
wg::DoublePerlinNoiseSampler::NoiseParameters{-9, {1.0,1.0,2.0,2.0,2.0,1.0,1.0,1.0,1.0}});
55+
wg::DoublePerlinNoiseSampler offset(randomDeriver.split("minecraft:offset"),
56+
wg::DoublePerlinNoiseSampler::NoiseParameters{-3, {1.0,1.0,1.0,0.0}});
57+
58+
// 收集 perm/origin(octBase 布局:continentalness 0..17,offset 18..25)
59+
const int totalOct = 18 + 8;
60+
std::vector<uint32_t> perm(totalOct * 256, 0);
61+
std::vector<double> origin(totalOct * 3, 0.0);
62+
collectNormal(continentalness, 0, perm, origin);
63+
collectNormal(offset, 18, perm, origin);
64+
65+
// 坐标(远坐标)
66+
const uint32_t N = 1024;
67+
std::vector<int32_t> coords(3 * N);
68+
for (uint32_t i = 0; i < N; i++) {
69+
coords[3*i+0] = (int32_t)(30000000 + (i % 16));
70+
coords[3*i+1] = (int32_t)(64 + (i / 16 % 16));
71+
coords[3*i+2] = (int32_t)(30000000 + (i / 256));
72+
}
73+
74+
// ---- Vulkan 初始化(fp64,4 buffer)----
75+
VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.apiVersion = VK_API_VERSION_1_3;
76+
VkInstanceCreateInfo instCI{}; instCI.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instCI.pApplicationInfo = &appInfo;
77+
VkInstance instance; CHECK_VK(vkCreateInstance(&instCI, nullptr, &instance));
78+
uint32_t devCount = 0; vkEnumeratePhysicalDevices(instance, &devCount, nullptr);
79+
std::vector<VkPhysicalDevice> phys(devCount); vkEnumeratePhysicalDevices(instance, &devCount, phys.data());
80+
VkPhysicalDevice physDev = phys[0];
81+
VkPhysicalDeviceProperties devProps; vkGetPhysicalDeviceProperties(physDev, &devProps);
82+
std::printf("[device] %s\n", devProps.deviceName);
83+
VkPhysicalDeviceFeatures feat{}; vkGetPhysicalDeviceFeatures(physDev, &feat);
84+
if (!feat.shaderFloat64) { std::fprintf(stderr, "fp64 not supported\n"); return 1; }
85+
uint32_t qCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(physDev, &qCount, nullptr);
86+
std::vector<VkQueueFamilyProperties> qProps(qCount); vkGetPhysicalDeviceQueueFamilyProperties(physDev, &qCount, qProps.data());
87+
uint32_t computeFamily = UINT32_MAX;
88+
for (uint32_t i = 0; i < qCount; i++) if (qProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { computeFamily = i; break; }
89+
float qPri = 1.0f;
90+
VkDeviceQueueCreateInfo qCI{}; qCI.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; qCI.queueFamilyIndex = computeFamily; qCI.queueCount = 1; qCI.pQueuePriorities = &qPri;
91+
VkPhysicalDeviceFeatures feat2{}; feat2.shaderFloat64 = VK_TRUE;
92+
VkDeviceCreateInfo devCI{}; devCI.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; devCI.queueCreateInfoCount = 1; devCI.pQueueCreateInfos = &qCI; devCI.pEnabledFeatures = &feat2;
93+
VkDevice device; CHECK_VK(vkCreateDevice(physDev, &devCI, nullptr, &device));
94+
VkQueue queue; vkGetDeviceQueue(device, computeFamily, 0, &queue);
95+
96+
auto spv = loadSpv("continents.spv");
97+
VkShaderModuleCreateInfo smCI{}; smCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; smCI.codeSize = spv.size()*4; smCI.pCode = spv.data();
98+
VkShaderModule shader; CHECK_VK(vkCreateShaderModule(device, &smCI, nullptr, &shader));
99+
VkDescriptorSetLayoutBinding bindings[4]{};
100+
for (int b = 0; b < 4; b++) { bindings[b].binding = b; bindings[b].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[b].descriptorCount = 1; bindings[b].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; }
101+
VkDescriptorSetLayoutCreateInfo dslCI{}; dslCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; dslCI.bindingCount = 4; dslCI.pBindings = bindings;
102+
VkDescriptorSetLayout dsl; CHECK_VK(vkCreateDescriptorSetLayout(device, &dslCI, nullptr, &dsl));
103+
VkPipelineLayoutCreateInfo plCI{}; plCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; plCI.setLayoutCount = 1; plCI.pSetLayouts = &dsl;
104+
VkPipelineLayout pipelineLayout; CHECK_VK(vkCreatePipelineLayout(device, &plCI, nullptr, &pipelineLayout));
105+
VkComputePipelineCreateInfo cpCI{}; cpCI.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
106+
cpCI.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; cpCI.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; cpCI.stage.module = shader; cpCI.stage.pName = "main"; cpCI.layout = pipelineLayout;
107+
VkPipeline pipeline; CHECK_VK(vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &cpCI, nullptr, &pipeline));
108+
109+
VkDeviceSize coordSize = coords.size() * sizeof(int32_t);
110+
VkDeviceSize permSize = perm.size() * sizeof(uint32_t);
111+
VkDeviceSize originSize = origin.size() * sizeof(double);
112+
VkDeviceSize outSize = N * sizeof(float);
113+
auto makeBuffer = [&](VkDeviceSize size, VkBuffer* buf, VkDeviceMemory* mem) {
114+
VkBufferCreateInfo bCI{}; bCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bCI.size = size; bCI.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; bCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
115+
CHECK_VK(vkCreateBuffer(device, &bCI, nullptr, buf));
116+
VkMemoryRequirements req; vkGetBufferMemoryRequirements(device, *buf, &req);
117+
VkPhysicalDeviceMemoryProperties mp; vkGetPhysicalDeviceMemoryProperties(physDev, &mp);
118+
uint32_t ti = UINT32_MAX; for (uint32_t i = 0; i < mp.memoryTypeCount; i++) if ((req.memoryTypeBits & (1u<<i)) && (mp.memoryTypes[i].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) { ti = i; break; }
119+
VkMemoryAllocateInfo aI{}; aI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; aI.allocationSize = req.size; aI.memoryTypeIndex = ti;
120+
CHECK_VK(vkAllocateMemory(device, &aI, nullptr, mem)); CHECK_VK(vkBindBufferMemory(device, *buf, *mem, 0));
121+
};
122+
VkBuffer coordBuf, permBuf, originBuf, outBuf; VkDeviceMemory coordMem, permMem, originMem, outMem;
123+
makeBuffer(coordSize, &coordBuf, &coordMem); makeBuffer(permSize, &permBuf, &permMem);
124+
makeBuffer(originSize, &originBuf, &originMem); makeBuffer(outSize, &outBuf, &outMem);
125+
{ void* m; CHECK_VK(vkMapMemory(device, coordMem, 0, coordSize, 0, &m)); std::memcpy(m, coords.data(), coordSize); vkUnmapMemory(device, coordMem); }
126+
{ void* m; CHECK_VK(vkMapMemory(device, permMem, 0, permSize, 0, &m)); std::memcpy(m, perm.data(), permSize); vkUnmapMemory(device, permMem); }
127+
{ void* m; CHECK_VK(vkMapMemory(device, originMem, 0, originSize, 0, &m)); std::memcpy(m, origin.data(), originSize); vkUnmapMemory(device, originMem); }
128+
129+
VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; poolSize.descriptorCount = 4;
130+
VkDescriptorPoolCreateInfo dpCI{}; dpCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; dpCI.maxSets = 1; dpCI.poolSizeCount = 1; dpCI.pPoolSizes = &poolSize;
131+
VkDescriptorPool dpool; CHECK_VK(vkCreateDescriptorPool(device, &dpCI, nullptr, &dpool));
132+
VkDescriptorSetAllocateInfo dsAI{}; dsAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; dsAI.descriptorPool = dpool; dsAI.descriptorSetCount = 1; dsAI.pSetLayouts = &dsl;
133+
VkDescriptorSet ds; CHECK_VK(vkAllocateDescriptorSets(device, &dsAI, &ds));
134+
VkDescriptorBufferInfo dbi[4]{{coordBuf,0,coordSize},{permBuf,0,permSize},{originBuf,0,originSize},{outBuf,0,outSize}};
135+
VkWriteDescriptorSet writes[4]{};
136+
for (int b = 0; b < 4; b++) { writes[b].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writes[b].dstSet = ds; writes[b].dstBinding = b; writes[b].descriptorCount = 1; writes[b].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; writes[b].pBufferInfo = &dbi[b]; }
137+
vkUpdateDescriptorSets(device, 4, writes, 0, nullptr);
138+
139+
VkCommandPoolCreateInfo cpPoolCI{}; cpPoolCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cpPoolCI.queueFamilyIndex = computeFamily;
140+
VkCommandPool cmdPool; CHECK_VK(vkCreateCommandPool(device, &cpPoolCI, nullptr, &cmdPool));
141+
VkCommandBufferAllocateInfo cbAI{}; cbAI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cbAI.commandPool = cmdPool; cbAI.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbAI.commandBufferCount = 1;
142+
VkCommandBuffer cb; CHECK_VK(vkAllocateCommandBuffers(device, &cbAI, &cb));
143+
VkCommandBufferBeginInfo begin{}; begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
144+
CHECK_VK(vkBeginCommandBuffer(cb, &begin));
145+
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
146+
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &ds, 0, nullptr);
147+
vkCmdDispatch(cb, (N + 255) / 256, 1, 1);
148+
CHECK_VK(vkEndCommandBuffer(cb));
149+
VkSubmitInfo submit{}; submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit.commandBufferCount = 1; submit.pCommandBuffers = &cb;
150+
VkFenceCreateInfo fenceCI{}; fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
151+
VkFence fence; CHECK_VK(vkCreateFence(device, &fenceCI, nullptr, &fence));
152+
CHECK_VK(vkQueueSubmit(queue, 1, &submit, fence));
153+
CHECK_VK(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX));
154+
155+
{ void* m; CHECK_VK(vkMapMemory(device, outMem, 0, outSize, 0, &m)); std::vector<float> out(N); std::memcpy(out.data(), m, outSize); vkUnmapMemory(device, outMem);
156+
double maxDiff = 0.0, sumDiff = 0.0; uint32_t maxIdx = 0;
157+
for (uint32_t i = 0; i < N; i++) {
158+
int x = coords[3*i+0], y = coords[3*i+1], z = coords[3*i+2];
159+
// CPU 参照(对齐 vanilla ShiftDF + ShiftedNoiseDF + NoiseDF)
160+
double shiftX = offset.sample(x * 0.25, 0.0, z * 0.25) * 4.0; // SHIFT_A: y=0
161+
double shiftZ = offset.sample(z * 0.25, x * 0.25, 0.0) * 4.0; // SHIFT_B: x=z,y=x,z=0
162+
double d = x * 0.25 + shiftX;
163+
double e = y * 0.0 + 0.0; // shift_y = 0
164+
double f = z * 0.25 + shiftZ;
165+
double ref = continentalness.sample(d, e, f);
166+
double diff = std::fabs((double)out[i] - ref);
167+
if (diff > maxDiff) { maxDiff = diff; maxIdx = i; }
168+
sumDiff += diff;
169+
}
170+
std::printf("[result] N=%u, continents DFC shader vs CPU double: maxDiff=%.3e avgDiff=%.3e\n", N, maxDiff, sumDiff / N);
171+
int x = coords[3*maxIdx], y = coords[3*maxIdx+1], z = coords[3*maxIdx+2];
172+
double shiftX = offset.sample(x * 0.25, 0.0, z * 0.25) * 4.0;
173+
double shiftZ = offset.sample(z * 0.25, x * 0.25, 0.0) * 4.0;
174+
std::printf("[result] maxDiff @ (%d,%d,%d): gpu=%.9f cpu=%.9f\n", x, y, z, out[maxIdx],
175+
continentalness.sample(x*0.25+shiftX, 0.0, z*0.25+shiftZ));
176+
}
177+
178+
vkDestroyFence(device, fence, nullptr); vkDestroyCommandPool(device, cmdPool, nullptr); vkDestroyDescriptorPool(device, dpool, nullptr);
179+
vkFreeMemory(device, coordMem, nullptr); vkFreeMemory(device, permMem, nullptr); vkFreeMemory(device, originMem, nullptr); vkFreeMemory(device, outMem, nullptr);
180+
vkDestroyBuffer(device, coordBuf, nullptr); vkDestroyBuffer(device, permBuf, nullptr); vkDestroyBuffer(device, originBuf, nullptr); vkDestroyBuffer(device, outBuf, nullptr);
181+
vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, dsl, nullptr); vkDestroyShaderModule(device, shader, nullptr);
182+
vkDestroyDevice(device, nullptr); vkDestroyInstance(instance, nullptr);
183+
std::printf("[done]\n");
184+
return 0;
185+
}

0 commit comments

Comments
 (0)