summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-03 19:06:41 +0100
committerskal <pascal.massimino@gmail.com>2026-02-03 19:06:41 +0100
commit3108fb0065a51dfc3548836ea16b287e92cd8881 (patch)
treeb20d3ffd904b65596ce9dd2df15a527b91a6539f /src
parentc3714939897af2541c655c03bcdd61108fff46ea (diff)
feat: side-quest - Perlin noise sky and ProcGenFunc error handling
- Updated ProcGenFunc signature to return bool for error reporting. - Implemented gen_perlin (Fractional Brownian Motion) in procedural/generator.cc. - Added support for sky texture in Renderer3D and its shader. - Integrated Perlin noise sky texture in test_3d_render.cc. - Caught and handled memory/generation errors in AssetManager and TextureManager. - Assigned reference numbers to all remaining tasks in documentation. handoff(Gemini): Side-quest complete. ProcGenFunc now returns bool. Perlin noise added and used for sky in 3D test. Windows build remains stable. All tasks numbered.
Diffstat (limited to 'src')
-rw-r--r--src/3d/renderer.cc32
-rw-r--r--src/3d/renderer.h2
-rw-r--r--src/gpu/texture_manager.cc14
-rw-r--r--src/gpu/texture_manager.h2
-rw-r--r--src/procedural/generator.cc175
-rw-r--r--src/procedural/generator.h20
-rw-r--r--src/tests/test_3d_render.cc15
-rw-r--r--src/util/asset_manager.cc13
-rw-r--r--src/util/asset_manager.h3
9 files changed, 217 insertions, 59 deletions
diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc
index cbc3cfa..f190646 100644
--- a/src/3d/renderer.cc
+++ b/src/3d/renderer.cc
@@ -101,13 +101,14 @@ void Renderer3D::create_default_resources() {
void Renderer3D::set_noise_texture(WGPUTextureView noise_view) {
noise_texture_view_ = noise_view;
- // Note: Bind group needs recreation if texture changes, but we'll do it in
- // render for simplicity or just once at init if it's static. For this demo,
- // let's recreate in render if changed.
+}
+
+void Renderer3D::set_sky_texture(WGPUTextureView sky_view) {
+ sky_texture_view_ = sky_view;
}
void Renderer3D::create_pipeline() {
- WGPUBindGroupLayoutEntry entries[4] = {};
+ WGPUBindGroupLayoutEntry entries[5] = {};
entries[0].binding = 0;
entries[0].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
entries[0].buffer.type = WGPUBufferBindingType_Uniform;
@@ -127,8 +128,13 @@ void Renderer3D::create_pipeline() {
entries[3].visibility = WGPUShaderStage_Fragment;
entries[3].sampler.type = WGPUSamplerBindingType_Filtering;
+ entries[4].binding = 4;
+ entries[4].visibility = WGPUShaderStage_Fragment;
+ entries[4].texture.sampleType = WGPUTextureSampleType_Float;
+ entries[4].texture.viewDimension = WGPUTextureViewDimension_2D;
+
WGPUBindGroupLayoutDescriptor bgl_desc = {};
- bgl_desc.entryCount = 4;
+ bgl_desc.entryCount = 5;
bgl_desc.entries = entries;
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc);
@@ -249,34 +255,28 @@ void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene,
if (bind_group_)
wgpuBindGroupRelease(bind_group_);
- WGPUBindGroupEntry bg_entries[4] = {};
+ WGPUBindGroupEntry bg_entries[5] = {};
bg_entries[0].binding = 0;
-
bg_entries[0].buffer = global_uniform_buffer_;
-
bg_entries[0].size = sizeof(GlobalUniforms);
bg_entries[1].binding = 1;
-
bg_entries[1].buffer = object_storage_buffer_;
-
bg_entries[1].size = sizeof(ObjectData) * kMaxObjects;
bg_entries[2].binding = 2;
-
bg_entries[2].textureView = noise_texture_view_;
bg_entries[3].binding = 3;
-
bg_entries[3].sampler = default_sampler_;
- WGPUBindGroupDescriptor bg_desc = {};
+ bg_entries[4].binding = 4;
+ bg_entries[4].textureView = sky_texture_view_ ? sky_texture_view_ : noise_texture_view_; // Fallback
+ WGPUBindGroupDescriptor bg_desc = {};
bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0);
-
- bg_desc.entryCount = 4;
-
+ bg_desc.entryCount = 5;
bg_desc.entries = bg_entries;
bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg_desc);
diff --git a/src/3d/renderer.h b/src/3d/renderer.h
index c6ca2a3..3caa329 100644
--- a/src/3d/renderer.h
+++ b/src/3d/renderer.h
@@ -57,6 +57,7 @@ class Renderer3D {
const Camera& camera, float time);
void set_noise_texture(WGPUTextureView noise_view);
+ void set_sky_texture(WGPUTextureView sky_view);
// Resize handler (if needed for internal buffers)
void resize(int width, int height);
@@ -76,6 +77,7 @@ class Renderer3D {
WGPUBuffer object_storage_buffer_ = nullptr;
WGPUTextureView noise_texture_view_ = nullptr;
+ WGPUTextureView sky_texture_view_ = nullptr;
WGPUSampler default_sampler_ = nullptr;
// Depth buffer management
diff --git a/src/gpu/texture_manager.cc b/src/gpu/texture_manager.cc
index 5da82c0..0c30c94 100644
--- a/src/gpu/texture_manager.cc
+++ b/src/gpu/texture_manager.cc
@@ -2,7 +2,7 @@
// It implements the TextureManager.
#include "gpu/texture_manager.h"
-#include <iostream>
+#include <cstdio>
#include <vector>
#if defined(DEMO_CROSS_COMPILE_WIN32)
@@ -33,14 +33,18 @@ void TextureManager::create_procedural_texture(
// 1. Generate Data on CPU
std::vector<uint8_t> pixel_data;
pixel_data.resize(def.width * def.height * 4);
- def.gen_func(pixel_data.data(), def.width, def.height, def.params.data(),
- (int)def.params.size());
+ if (!def.gen_func(pixel_data.data(), def.width, def.height, def.params.data(),
+ (int)def.params.size())) {
+ fprintf(stderr, "Error: Procedural texture generation failed for: %s\n",
+ name.c_str());
+ return;
+ }
create_texture(name, def.width, def.height, pixel_data.data());
#if !defined(STRIP_ALL)
- std::cout << "Generated procedural texture: " << name << " (" << def.width
- << "x" << def.height << ")" << std::endl;
+ printf("Generated procedural texture: %s (%dx%d)\n", name.c_str(), def.width,
+ def.height);
#endif
}
diff --git a/src/gpu/texture_manager.h b/src/gpu/texture_manager.h
index f49e827..23fdbe8 100644
--- a/src/gpu/texture_manager.h
+++ b/src/gpu/texture_manager.h
@@ -12,7 +12,7 @@
struct ProceduralTextureDef {
int width;
int height;
- void (*gen_func)(uint8_t*, int, int, const float*, int);
+ bool (*gen_func)(uint8_t*, int, int, const float*, int);
std::vector<float> params;
};
diff --git a/src/procedural/generator.cc b/src/procedural/generator.cc
index 5ae83e4..0eb8b03 100644
--- a/src/procedural/generator.cc
+++ b/src/procedural/generator.cc
@@ -15,65 +15,204 @@ constexpr float mix(float a, float b, float t) {
return (a * (1.0f - t) + b * t);
}
+// Perlin noise generator (Fractional Brownian Motion using value noise)
+// Params[0]: Seed
+// Params[1]: Frequency (Scale)
+// Params[2]: Amplitude
+// Params[3]: Amplitude decay
+// Params[4]: Number of octaves
+bool gen_perlin(uint8_t* buffer, int w, int h, const float* params,
+ int num_params) {
+ float base_freq = (num_params > 1) ? params[1] : 4.0f;
+ float base_amp = (num_params > 2) ? params[2] : 1.0f;
+ float amp_decay = (num_params > 3) ? params[3] : 0.5f;
+ int octaves = (num_params > 4) ? (int)params[4] : 4;
+ if (num_params > 0 && params[0] != 0) {
+ srand((unsigned int)params[0]);
+ }
+
+ // Pre-allocate temporary float buffer for accumulating noise
+ float* accum = (float*)calloc((size_t)w * h, sizeof(float));
+ if (!accum) return false;
+
+ float current_freq = base_freq;
+ float current_amp = base_amp;
+ float total_amp = 0.0f;
+
+ for (int o = 0; o < octaves; ++o) {
+ const int lattice_w = (int)ceil(current_freq) + 1;
+ const int lattice_h = (int)ceil(current_freq) + 1;
+ float* lattice = (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float));
+ if (!lattice) {
+ free(accum);
+ return false;
+ }
+
+ for (int i = 0; i < lattice_w * lattice_h; ++i) {
+ lattice[i] = (float)rand() / RAND_MAX;
+ }
+
+ const float scale_u = current_freq / w;
+ const float scale_v = current_freq / h;
+
+ for (int y = 0; y < h; ++y) {
+ const float v = scale_v * y;
+ const int ly = (int)floor(v);
+ const int ly_next = (ly + 1); // No wrap here for better octaves
+ float fv = smooth(v - ly);
+ for (int x = 0; x < w; ++x) {
+ float u = scale_u * x;
+ const int lx = (int)floor(u);
+ const int lx_next = (lx + 1);
+ float fu = smooth(u - lx);
+
+ // Simple tiling for lattice access
+ auto get_lat = [&](int ix, int iy) {
+ return lattice[(iy % lattice_h) * lattice_w + (ix % lattice_w)];
+ };
+
+ float n00 = get_lat(lx, ly);
+ float n10 = get_lat(lx_next, ly);
+ float n01 = get_lat(lx, ly_next);
+ float n11 = get_lat(lx_next, ly_next);
+
+ const float noise = mix(mix(n00, n10, fu), mix(n01, n11, fu), fv);
+ accum[y * w + x] += noise * current_amp;
+ }
+ }
+
+ total_amp += current_amp;
+ current_freq *= 2.0f;
+ current_amp *= amp_decay;
+ free(lattice);
+ }
+
+ // Normalize and write to RGBA buffer
+ for (int i = 0; i < w * h; ++i) {
+ float val = accum[i] / total_amp;
+ uint8_t uval = (uint8_t)(fminf(fmaxf(val, 0.0f), 1.0f) * 255.0f);
+ buffer[4 * i + 0] = uval;
+ buffer[4 * i + 1] = uval;
+ buffer[4 * i + 2] = uval;
+ buffer[4 * i + 3] = 255;
+ }
+
+ free(accum);
+ return true;
+}
+
// Simple smooth noise generator (Value Noise-ish)
+
// Params[0]: Seed
+
// Params[1]: Frequency (Scale)
-void gen_noise(uint8_t* buffer, int w, int h, const float* params,
+
+bool gen_noise(uint8_t* buffer, int w, int h, const float* params,
+
int num_params) {
+
float freq = (num_params > 1) ? params[1] : 4.0f;
+
if (num_params > 0 && params[0] != 0) {
+
srand((unsigned int)params[0]);
+
}
+
+
// Create a small lattice of random values
+
const int lattice_w = (int)ceil(freq);
+
const int lattice_h = (int)ceil(freq);
- float* lattice =
- (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float));
- if (!lattice)
- return;
+
+ float* lattice = (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float));
+
+ if (!lattice) return false;
+
+
for (int i = 0; i < lattice_w * lattice_h; ++i) {
+
lattice[i] = (float)rand() / RAND_MAX;
+
}
+
const float scale_u = 1.f * (lattice_w - 1) / w;
+
const float scale_v = 1.f * (lattice_h - 1) / h;
+
+
for (int y = 0; y < h; ++y) {
+
const float v = scale_v * y;
+
const int ly = (int)floor(v);
+
const int ly_next = (ly + 1) % lattice_h; // Wrap
+
const float* const n0 = &lattice[ly * lattice_w];
+
const float* const n1 = &lattice[ly_next * lattice_w];
+
float fv = smooth(v - ly);
+
uint8_t* const dst = &buffer[y * w * 4];
+
for (int x = 0; x < w; ++x) {
+
float u = scale_u * x;
+
const int lx = (int)floor(u);
+
const int lx_next = (lx + 1) % lattice_w;
+
float fu = smooth(u - lx);
+
+
float n00 = n0[lx];
+
float n10 = n0[lx_next];
+
float n01 = n1[lx];
+
float n11 = n1[lx_next];
+
+
const float noise = mix(mix(n00, n10, fu), mix(n01, n11, fu), fv);
+
+
const uint8_t val = (uint8_t)(noise * 255.0f);
+
dst[4 * x + 0] = val; // R
+
dst[4 * x + 1] = val; // G
+
dst[4 * x + 2] = val; // B
+
dst[4 * x + 3] = 255; // A
+
}
+
}
+
free(lattice);
+
+ return true;
+
}
+
+
// Simple grid generator
// Params[0]: Grid Size (pixels)
// Params[1]: Line Thickness (pixels)
-void gen_grid(uint8_t* buffer, int w, int h, const float* params,
+bool gen_grid(uint8_t* buffer, int w, int h, const float* params,
int num_params) {
int grid_size = (num_params > 0) ? (int)params[0] : 32;
int thickness = (num_params > 1) ? (int)params[1] : 2;
@@ -95,13 +234,14 @@ void gen_grid(uint8_t* buffer, int w, int h, const float* params,
buffer[idx + 3] = 255;
}
}
+ return true;
}
-void make_periodic(uint8_t* buffer, int w, int h, const float* params,
+bool make_periodic(uint8_t* buffer, int w, int h, const float* params,
int num_params) {
float ratio = (num_params > 0) ? params[0] : 0.1f;
if (ratio <= 0.0f)
- return;
+ return true;
if (ratio > 0.5f)
ratio = 0.5f;
@@ -124,24 +264,6 @@ void make_periodic(uint8_t* buffer, int w, int h, const float* params,
buffer[idx_dst + c] = (uint8_t)mix(v_src, v_dst, t);
}
}
- // Copy left edge back to right edge to ensure perfect pixel match?
- // Actually, texture wrapping usually means buffer[w] ~ buffer[0].
- // buffer[w-1] should neighbor buffer[0].
- // With above logic: buffer[0] is blend(buffer[w-bx], buffer[0], 0) =
- // buffer[w-bx]. So buffer[0] looks like the pixel at w-bx. This effectively
- // shrinks the texture content by bx? A bit hacky but works for noise. To be
- // seamless at w-1 -> 0: buffer[w-1] is original. buffer[0] matches
- // buffer[w-bx]. There is still a jump at w-1 -> 0 if buffer[w-1] !=
- // buffer[w-bx-1]?
- //
- // Improved logic: Blend BOTH sides to the average?
- // Let's modify the right side too.
- for (int x = 0; x < bx; ++x) {
- // We want buffer[w-bx+x] to blend towards buffer[x] (which is now
- // modified? No, original) This is getting complicated. The simple "mix
- // right side into left side" works if the texture frequency is high
- // enough. Let's just stick to the simple one requested.
- }
}
// Y pass
@@ -158,6 +280,7 @@ void make_periodic(uint8_t* buffer, int w, int h, const float* params,
}
}
}
+ return true;
}
} // namespace procedural
diff --git a/src/procedural/generator.h b/src/procedural/generator.h
index 72682b0..e57ef61 100644
--- a/src/procedural/generator.h
+++ b/src/procedural/generator.h
@@ -10,22 +10,32 @@
// buffer: Pointer to RGBA8 buffer (size w * h * 4)
// w, h: Dimensions
// params: Arbitrary float parameters for the generator
-typedef void (*ProcGenFunc)(uint8_t* buffer, int w, int h, const float* params,
+// Returns true on success, false on failure.
+typedef bool (*ProcGenFunc)(uint8_t* buffer, int w, int h, const float* params,
int num_params);
namespace procedural {
-// Example: Simple noise generator
-void gen_noise(uint8_t* buffer, int w, int h, const float* params,
+// Simple noise generator
+bool gen_noise(uint8_t* buffer, int w, int h, const float* params,
int num_params);
+// Perlin noise generator
+// Params[0]: Seed
+// Params[1]: Frequency (Scale)
+// Params[2]: Amplitude
+// Params[3]: Amplitude decay (e.g. 0.5)
+// Params[4]: Number of octaves (int)
+bool gen_perlin(uint8_t* buffer, int w, int h, const float* params,
+ int num_params);
+
// Example: Grid pattern
-void gen_grid(uint8_t* buffer, int w, int h, const float* params,
+bool gen_grid(uint8_t* buffer, int w, int h, const float* params,
int num_params);
// Post-process: Make texture periodic by blending edges
// Params[0]: Border size ratio (0.0 - 0.5), default 0.1
-void make_periodic(uint8_t* buffer, int w, int h, const float* params,
+bool make_periodic(uint8_t* buffer, int w, int h, const float* params,
int num_params);
} // namespace procedural
diff --git a/src/tests/test_3d_render.cc b/src/tests/test_3d_render.cc
index 6e639cd..024dd87 100644
--- a/src/tests/test_3d_render.cc
+++ b/src/tests/test_3d_render.cc
@@ -167,11 +167,12 @@ void setup_scene() {
}
// Wrapper to generate periodic noise
-void gen_periodic_noise(uint8_t* buffer, int w, int h, const float* params,
+bool gen_periodic_noise(uint8_t* buffer, int w, int h, const float* params,
int num_params) {
- procedural::gen_noise(buffer, w, h, params, num_params);
+ if (!procedural::gen_noise(buffer, w, h, params, num_params))
+ return false;
float p_params[] = {0.1f}; // 10% overlap
- procedural::make_periodic(buffer, w, h, p_params, 1);
+ return procedural::make_periodic(buffer, w, h, p_params, 1);
}
int main(int argc, char** argv) {
@@ -209,6 +210,14 @@ int main(int argc, char** argv) {
g_renderer.set_noise_texture(g_textures.get_texture_view("noise"));
+ ProceduralTextureDef sky_def;
+ sky_def.width = 512;
+ sky_def.height = 256;
+ sky_def.gen_func = procedural::gen_perlin;
+ sky_def.params = {42.0f, 4.0f, 1.0f, 0.5f, 6.0f};
+ g_textures.create_procedural_texture("sky", sky_def);
+ g_renderer.set_sky_texture(g_textures.get_texture_view("sky"));
+
setup_scene();
g_camera.position = vec3(0, 5, 10);
diff --git a/src/util/asset_manager.cc b/src/util/asset_manager.cc
index 9294560..ce495ac 100644
--- a/src/util/asset_manager.cc
+++ b/src/util/asset_manager.cc
@@ -23,6 +23,7 @@ struct ProcGenEntry {
static const ProcGenEntry kAssetManagerProcGenFuncs[] = {
{"gen_noise", procedural::gen_noise},
+ {"gen_perlin", procedural::gen_perlin},
{"gen_grid", procedural::gen_grid},
{"make_periodic", procedural::make_periodic},
};
@@ -103,8 +104,16 @@ const uint8_t* GetAsset(AssetId asset_id, size_t* out_size) {
*out_size = 0;
return nullptr;
}
- proc_gen_func_ptr(generated_data, width, height, source_record.proc_params,
- source_record.num_proc_params);
+ if (!proc_gen_func_ptr(generated_data, width, height,
+ source_record.proc_params,
+ source_record.num_proc_params)) {
+ fprintf(stderr, "Error: Procedural generation failed for asset: %s\n",
+ source_record.proc_func_name_str);
+ delete[] generated_data;
+ if (out_size)
+ *out_size = 0;
+ return nullptr;
+ }
cached_record.data = generated_data;
cached_record.size = data_size;
diff --git a/src/util/asset_manager.h b/src/util/asset_manager.h
index 062cd0f..964b7af 100644
--- a/src/util/asset_manager.h
+++ b/src/util/asset_manager.h
@@ -10,7 +10,8 @@ enum class AssetId : uint16_t; // Forward declaration
// Type for procedural generation functions: (buffer, width, height, params,
// num_params)
-typedef void (*ProcGenFunc)(uint8_t*, int, int, const float*, int);
+// Returns true on success, false on failure.
+typedef bool (*ProcGenFunc)(uint8_t*, int, int, const float*, int);
struct AssetRecord {
const uint8_t* data; // Pointer to asset data (static or dynamic)