From 8bdc4754647c9c6691130fa91d51fee93c5fc88f Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 1 Feb 2026 10:51:15 +0100 Subject: feat: Implement 3D system and procedural texture manager - Extended mini_math.h with mat4 multiplication and affine transforms. - Implemented TextureManager for runtime procedural texture generation and GPU upload. - Added 3D system components: Camera, Object, Scene, and Renderer3D. - Created test_3d_render mini-demo for interactive 3D verification. - Fixed WebGPU validation errors regarding depthSlice and unimplemented WaitAny. --- src/tests/test_3d_render.cc | 226 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 src/tests/test_3d_render.cc (limited to 'src/tests/test_3d_render.cc') diff --git a/src/tests/test_3d_render.cc b/src/tests/test_3d_render.cc new file mode 100644 index 0000000..41bffe6 --- /dev/null +++ b/src/tests/test_3d_render.cc @@ -0,0 +1,226 @@ +// This file is part of the 64k demo project. +// Standalone "mini-demo" for testing the 3D renderer. + +#include "3d/camera.h" +#include "3d/object.h" +#include "3d/renderer.h" +#include "3d/scene.h" +#include "platform.h" +#include +#include +#include +#include + +#if defined(DEMO_CROSS_COMPILE_WIN32) +#include +#else +#include +#endif + +// Global State +static Renderer3D g_renderer; +static Scene g_scene; +static Camera g_camera; +static WGPUDevice g_device = nullptr; +static WGPUQueue g_queue = nullptr; +static WGPUSurface g_surface = nullptr; +static WGPUAdapter g_adapter = nullptr; +static WGPUTextureFormat g_format = WGPUTextureFormat_Undefined; +static int g_width = 1280; +static int g_height = 720; + +// Reimplementing basic WebGPU init here +void init_wgpu() { + WGPUInstance instance = wgpuCreateInstance(nullptr); + if (!instance) { + std::cerr << "Failed to create WGPU instance." << std::endl; + exit(1); + } + + g_surface = platform_create_wgpu_surface(instance); + if (!g_surface) { + std::cerr << "Failed to create WGPU surface." << std::endl; + exit(1); + } + + WGPURequestAdapterOptions adapter_opts = {}; + adapter_opts.compatibleSurface = g_surface; + adapter_opts.powerPreference = WGPUPowerPreference_HighPerformance; + +#if defined(DEMO_CROSS_COMPILE_WIN32) + auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, + const char* message, void* userdata) { + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = adapter; + } else { + std::cerr << "Adapter Error: " << (message ? message : "null") << std::endl; + } + }; + wgpuInstanceRequestAdapter(instance, &adapter_opts, on_adapter, &g_adapter); +#else + auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, + WGPUStringView message, void* userdata, void* user2) { + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = adapter; + } else { + std::cerr << "Adapter Error: " << (message.data ? message.data : "null") << std::endl; + } + }; + WGPURequestAdapterCallbackInfo adapter_cb = {}; + adapter_cb.mode = WGPUCallbackMode_WaitAnyOnly; + adapter_cb.callback = on_adapter; + adapter_cb.userdata1 = &g_adapter; + wgpuInstanceRequestAdapter(instance, &adapter_opts, adapter_cb); +#endif + + // Spin wait for adapter +#if !defined(DEMO_CROSS_COMPILE_WIN32) + while (!g_adapter) { + wgpuInstanceProcessEvents(instance); + } +#endif + + if (!g_adapter) { + std::cerr << "Failed to get adapter." << std::endl; + exit(1); + } + + WGPUDeviceDescriptor device_desc = {}; + +#if defined(DEMO_CROSS_COMPILE_WIN32) + auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice device, + const char* message, void* userdata) { + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = device; + } + }; + wgpuAdapterRequestDevice(g_adapter, &device_desc, on_device, &g_device); +#else + auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice device, + WGPUStringView message, void* userdata, void* user2) { + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = device; + } + }; + WGPURequestDeviceCallbackInfo device_cb = {}; + device_cb.mode = WGPUCallbackMode_WaitAnyOnly; + device_cb.callback = on_device; + device_cb.userdata1 = &g_device; + wgpuAdapterRequestDevice(g_adapter, &device_desc, device_cb); +#endif + +#if !defined(DEMO_CROSS_COMPILE_WIN32) + // Poll until device is ready (WaitAny is unimplemented in current wgpu-native build) + while (!g_device) { + wgpuInstanceProcessEvents(instance); + } +#endif + + if (!g_device) { + std::cerr << "Failed to get device." << std::endl; + exit(1); + } + + g_queue = wgpuDeviceGetQueue(g_device); + + WGPUSurfaceCapabilities caps = {}; + wgpuSurfaceGetCapabilities(g_surface, g_adapter, &caps); + g_format = caps.formats[0]; + + WGPUSurfaceConfiguration config = {}; + config.device = g_device; + config.format = g_format; + config.usage = WGPUTextureUsage_RenderAttachment; + config.width = g_width; + config.height = g_height; + config.presentMode = WGPUPresentMode_Fifo; + config.alphaMode = WGPUCompositeAlphaMode_Opaque; + wgpuSurfaceConfigure(g_surface, &config); +} + +void setup_scene() { + g_scene.clear(); + // Center Red Cube + Object3D center; + center.position = vec3(0, 0, 0); + center.color = vec4(1, 0, 0, 1); + g_scene.add_object(center); + + // Orbiting Green Cubes + for (int i = 0; i < 8; ++i) { + Object3D obj; + float angle = (i / 8.0f) * 6.28318f; + obj.position = vec3(std::cos(angle) * 4.0f, 0, std::sin(angle) * 4.0f); + obj.scale = vec3(0.5f, 0.5f, 0.5f); + obj.color = vec4(0, 1, 0, 1); + g_scene.add_object(obj); + } +} + +int main() { + platform_init_window(false); + + init_wgpu(); + + g_renderer.init(g_device, g_queue, g_format); + g_renderer.resize(g_width, g_height); + + setup_scene(); + + g_camera.position = vec3(0, 5, 10); + g_camera.target = vec3(0, 0, 0); + + float time = 0.0f; + while (!platform_should_close()) { + platform_poll(); + + time += 0.016f; // Approx 60fps + + // Animate Objects + for (size_t i = 1; i < g_scene.objects.size(); ++i) { + g_scene.objects[i].rotation = quat::from_axis(vec3(0, 1, 0), time * 2.0f + i); + g_scene.objects[i].position.y = std::sin(time * 3.0f + i) * 1.5f; + } + + // Animate Camera Height and Radius + float cam_radius = 10.0f + std::sin(time * 0.3f) * 4.0f; + float cam_height = 5.0f + std::cos(time * 0.4f) * 3.0f; + g_camera.set_look_at( + vec3(std::sin(time * 0.5f) * cam_radius, cam_height, std::cos(time * 0.5f) * cam_radius), + vec3(0, 0, 0), + vec3(0, 1, 0) + ); + + // Render Frame + WGPUSurfaceTexture surface_tex; + wgpuSurfaceGetCurrentTexture(g_surface, &surface_tex); + + if (surface_tex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) { + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = g_format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.baseMipLevel = 0; + view_desc.mipLevelCount = 1; + view_desc.baseArrayLayer = 0; + view_desc.arrayLayerCount = 1; + view_desc.aspect = WGPUTextureAspect_All; + WGPUTextureView view = wgpuTextureCreateView(surface_tex.texture, &view_desc); + + g_renderer.render(g_scene, g_camera, time, view); + + wgpuTextureViewRelease(view); + wgpuSurfacePresent(g_surface); + wgpuTextureRelease(surface_tex.texture); + } + +#if !defined(DEMO_CROSS_COMPILE_WIN32) + // Poll events for wgpu-native to ensure callbacks fire and frame presents? + // We don't have easy access to instance here unless we store it globally. + // Let's just assume Present handles enough synchronization for this demo. +#endif + } + + g_renderer.shutdown(); + platform_shutdown(); + return 0; +} -- cgit v1.2.3