From a162c98ce54159e3e7dbe867d908ce3276b7f633 Mon Sep 17 00:00:00 2001 From: grothedev Date: Thu, 2 Oct 2025 01:27:50 -0400 Subject: trying c++ impl --- .gitignore | 1 + AGENTS.md | 10 ++ cpp-timeplot/CMakeLists.txt | 87 +++++++++++++++++ cpp-timeplot/README.md | 32 +++++++ cpp-timeplot/shaders/waterfall.wgsl | 22 +++++ cpp-timeplot/src/main.cpp | 143 ++++++++++++++++++++++++++++ cpp-timeplot/src/renderer.cpp | 180 ++++++++++++++++++++++++++++++++++++ cpp-timeplot/src/renderer.h | 35 +++++++ cpp-timeplot/src/waterfall.cpp | 164 ++++++++++++++++++++++++++++++++ cpp-timeplot/src/waterfall.h | 39 ++++++++ cpp-timeplot/src/webgpu_impl.cpp | 2 + 11 files changed, 715 insertions(+) create mode 100644 cpp-timeplot/CMakeLists.txt create mode 100644 cpp-timeplot/README.md create mode 100644 cpp-timeplot/shaders/waterfall.wgsl create mode 100644 cpp-timeplot/src/main.cpp create mode 100644 cpp-timeplot/src/renderer.cpp create mode 100644 cpp-timeplot/src/renderer.h create mode 100644 cpp-timeplot/src/waterfall.cpp create mode 100644 cpp-timeplot/src/waterfall.h create mode 100644 cpp-timeplot/src/webgpu_impl.cpp diff --git a/.gitignore b/.gitignore index ea8c4bf..fd85fde 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/cpp-timeplot/build/ diff --git a/AGENTS.md b/AGENTS.md index 96e4bff..0ceee95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,3 +40,13 @@ there shall be a state management system similar to js react, whereby UI element ### GUI details - TODO + + + +## Next Steps +- Rust + - split code into modules + - fix text label title + - add plot labels and legends +- C++ + - initial implementation diff --git a/cpp-timeplot/CMakeLists.txt b/cpp-timeplot/CMakeLists.txt new file mode 100644 index 0000000..58993dd --- /dev/null +++ b/cpp-timeplot/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.20) +project(timeplot-cpp VERSION 0.1.0) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Find dependencies +find_package(PkgConfig REQUIRED) + +# GLFW for windowing +pkg_check_modules(GLFW REQUIRED glfw3) + +# Use precompiled Dawn from Chromium +option(USE_SYSTEM_DAWN "Use system-installed Dawn" OFF) + +if(USE_SYSTEM_DAWN) + find_package(dawn REQUIRED) +else() + # Download precompiled Dawn binaries + message(STATUS "Downloading precompiled Dawn...") + + set(DAWN_VERSION "6536") + set(DAWN_DIR "${CMAKE_BINARY_DIR}/dawn-prebuilt") + + if(NOT EXISTS "${DAWN_DIR}") + # Try to download from a prebuilt source + # Note: Dawn doesn't officially provide prebuilt binaries, so we'll build once and cache + # For now, let's use webgpu-distribution which provides prebuilt headers + include(FetchContent) + + # Fetch webgpu-distribution (lighter than full Dawn) + FetchContent_Declare( + webgpu-distribution + GIT_REPOSITORY https://github.com/eliemichel/WebGPU-distribution + GIT_TAG main + GIT_SHALLOW TRUE + ) + + FetchContent_MakeAvailable(webgpu-distribution) + + set(WEBGPU_BACKEND "WGPU" CACHE STRING "Backend to use") + + # Fetch webgpu-hpp (C++ wrapper) + FetchContent_Declare( + webgpu-hpp + GIT_REPOSITORY https://github.com/eliemichel/WebGPU-Cpp + GIT_TAG main + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(webgpu-hpp) + + # Fetch glfw3webgpu (GLFW-WebGPU integration) + FetchContent_Declare( + glfw3webgpu + GIT_REPOSITORY https://github.com/eliemichel/glfw3webgpu + GIT_TAG main + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(glfw3webgpu) + endif() +endif() + +# Source files +set(SOURCES + src/main.cpp + src/renderer.cpp + src/waterfall.cpp + src/webgpu_impl.cpp +) + +add_executable(timeplot ${SOURCES}) + +target_include_directories(timeplot PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${GLFW_INCLUDE_DIRS} +) + +target_link_libraries(timeplot PRIVATE + ${GLFW_LIBRARIES} + webgpu + glfw3webgpu +) + +# Copy shaders to build directory +file(GLOB SHADERS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.wgsl") +file(COPY ${SHADERS} DESTINATION ${CMAKE_BINARY_DIR}/shaders) diff --git a/cpp-timeplot/README.md b/cpp-timeplot/README.md new file mode 100644 index 0000000..2700f9f --- /dev/null +++ b/cpp-timeplot/README.md @@ -0,0 +1,32 @@ +# TimePlot C++ Implementation + +WebGPU-based waterfall plot renderer using Dawn and GLFW. + +## Dependencies + +- CMake 3.20+ +- C++20 compiler +- GLFW3 +- Dawn (fetched automatically by CMake) + +## Build + +```bash +mkdir build && cd build +cmake .. +make -j$(nproc) +./timeplot +``` + +## Controls + +- `G` - Toggle grid visibility +- `ESC` - Exit application + +## Features + +- Multi-threaded GPU rendering via Vulkan/WebGPU +- Multiple graph views (side-by-side) +- Animated waterfall plots +- Grid lines and borders +- Real-time data visualization diff --git a/cpp-timeplot/shaders/waterfall.wgsl b/cpp-timeplot/shaders/waterfall.wgsl new file mode 100644 index 0000000..6655301 --- /dev/null +++ b/cpp-timeplot/shaders/waterfall.wgsl @@ -0,0 +1,22 @@ +struct VertexInput { + @location(0) position: vec2, + @location(1) color: vec3, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec3, +} + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.clip_position = vec4(in.position, 0.0, 1.0); + out.color = in.color; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(in.color, 1.0); +} diff --git a/cpp-timeplot/src/main.cpp b/cpp-timeplot/src/main.cpp new file mode 100644 index 0000000..7afd3e8 --- /dev/null +++ b/cpp-timeplot/src/main.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include + +#include "renderer.h" + +constexpr int WINDOW_WIDTH = 1280; +constexpr int WINDOW_HEIGHT = 720; + +class Application { +public: + Application() : window_(nullptr) {} + + ~Application() { + cleanup(); + } + + bool initialize() { + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + return false; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + window_ = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, + "TimePlot - C++ WebGPU", nullptr, nullptr); + if (!window_) { + std::cerr << "Failed to create window" << std::endl; + return false; + } + + glfwSetWindowUserPointer(window_, this); + glfwSetKeyCallback(window_, keyCallback); + glfwSetFramebufferSizeCallback(window_, resizeCallback); + + if (!initWebGPU()) { + return false; + } + + renderer_ = std::make_unique(device_, surface_, WINDOW_WIDTH, WINDOW_HEIGHT); + if (!renderer_->initialize()) { + std::cerr << "Failed to initialize renderer" << std::endl; + return false; + } + + return true; + } + + void run() { + while (!glfwWindowShouldClose(window_)) { + glfwPollEvents(); + + renderer_->update(); + renderer_->render(); + } + } + +private: + bool initWebGPU() { + // Create instance + instance_ = wgpu::createInstance(wgpu::InstanceDescriptor{}); + if (!instance_) { + std::cerr << "Failed to create WebGPU instance" << std::endl; + return false; + } + + // Create surface from GLFW window + surface_ = glfwCreateWindowWGPUSurface(instance_, window_); + if (!surface_) { + std::cerr << "Failed to create surface" << std::endl; + return false; + } + + // Request adapter (synchronous version) + wgpu::RequestAdapterOptions adapterOpts{}; + adapterOpts.compatibleSurface = surface_; + + wgpu::Adapter adapter = instance_.requestAdapter(adapterOpts); + if (!adapter) { + std::cerr << "Failed to get adapter" << std::endl; + return false; + } + + // Request device (synchronous version) + wgpu::DeviceDescriptor deviceDesc{}; + device_ = adapter.requestDevice(deviceDesc); + + if (!device_) { + std::cerr << "Failed to get device" << std::endl; + return false; + } + + return true; + } + + void cleanup() { + renderer_.reset(); + + if (window_) { + glfwDestroyWindow(window_); + window_ = nullptr; + } + glfwTerminate(); + } + + static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (action == GLFW_PRESS) { + if (key == GLFW_KEY_ESCAPE) { + glfwSetWindowShouldClose(window, GLFW_TRUE); + } else if (key == GLFW_KEY_G) { + app->renderer_->toggleGrid(); + } + } + } + + static void resizeCallback(GLFWwindow* window, int width, int height) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + app->renderer_->resize(width, height); + } + + GLFWwindow* window_; + wgpu::Instance instance_; + wgpu::Device device_; + wgpu::Surface surface_; + std::unique_ptr renderer_; +}; + +int main() { + Application app; + + if (!app.initialize()) { + return EXIT_FAILURE; + } + + app.run(); + return EXIT_SUCCESS; +} diff --git a/cpp-timeplot/src/renderer.cpp b/cpp-timeplot/src/renderer.cpp new file mode 100644 index 0000000..1c61698 --- /dev/null +++ b/cpp-timeplot/src/renderer.cpp @@ -0,0 +1,180 @@ +#include "renderer.h" +#include "waterfall.h" +#include +#include +#include + +Renderer::Renderer(wgpu::Device device, wgpu::Surface surface, int width, int height) + : device_(device), surface_(surface), width_(width), height_(height), time_(0.0f), + surfaceFormat_(wgpu::TextureFormat::BGRA8Unorm) {} + +Renderer::~Renderer() = default; + +bool Renderer::initialize() { + configureSurface(); + createPipelines(); + + // Create two waterfall views side-by-side + waterfalls_.push_back(std::make_unique( + device_, 0.0f, 0.1f, 0.5f, 0.9f, "Frequency vs Time")); + waterfalls_.push_back(std::make_unique( + device_, 0.5f, 0.1f, 0.5f, 0.9f, "Position vs Time")); + + for (auto& waterfall : waterfalls_) { + if (!waterfall->initialize()) { + return false; + } + } + + return true; +} + +void Renderer::configureSurface() { + wgpu::SurfaceConfiguration config{}; + config.device = device_; + config.format = surfaceFormat_; + config.usage = wgpu::TextureUsage::RenderAttachment; + config.width = width_; + config.height = height_; + config.presentMode = wgpu::PresentMode::Fifo; + config.alphaMode = wgpu::CompositeAlphaMode::Auto; + + surface_.configure(config); +} + +void Renderer::createPipelines() { + // Load shader + std::ifstream shaderFile("shaders/waterfall.wgsl"); + std::stringstream buffer; + buffer << shaderFile.rdbuf(); + std::string shaderCode = buffer.str(); + + wgpu::ShaderSourceWGSL wgslSource{}; + wgslSource.chain.sType = wgpu::SType::ShaderSourceWGSL; + wgslSource.code.data = shaderCode.c_str(); + wgslSource.code.length = shaderCode.length(); + + wgpu::ShaderModuleDescriptor shaderDesc{}; + shaderDesc.nextInChain = &wgslSource.chain; + wgpu::ShaderModule shader = device_.createShaderModule(shaderDesc); + + // Vertex buffer layout + wgpu::VertexAttribute attributes[2]; + attributes[0].format = wgpu::VertexFormat::Float32x2; + attributes[0].offset = 0; + attributes[0].shaderLocation = 0; + + attributes[1].format = wgpu::VertexFormat::Float32x3; + attributes[1].offset = 2 * sizeof(float); + attributes[1].shaderLocation = 1; + + wgpu::VertexBufferLayout vertexBufferLayout{}; + vertexBufferLayout.arrayStride = 5 * sizeof(float); + vertexBufferLayout.stepMode = wgpu::VertexStepMode::Vertex; + vertexBufferLayout.attributeCount = 2; + vertexBufferLayout.attributes = attributes; + + // Pipeline layout + wgpu::PipelineLayoutDescriptor layoutDesc{}; + wgpu::PipelineLayout pipelineLayout = device_.createPipelineLayout(layoutDesc); + + // Color target + wgpu::BlendState blend{}; + blend.color.operation = wgpu::BlendOperation::Add; + blend.color.srcFactor = wgpu::BlendFactor::One; + blend.color.dstFactor = wgpu::BlendFactor::Zero; + blend.alpha.operation = wgpu::BlendOperation::Add; + blend.alpha.srcFactor = wgpu::BlendFactor::One; + blend.alpha.dstFactor = wgpu::BlendFactor::Zero; + + wgpu::ColorTargetState colorTarget{}; + colorTarget.format = surfaceFormat_; + colorTarget.blend = &blend; + colorTarget.writeMask = wgpu::ColorWriteMask::All; + + wgpu::FragmentState fragmentState{}; + fragmentState.module = shader; + fragmentState.entryPoint.data = "fs_main"; + fragmentState.entryPoint.length = 7; + fragmentState.targetCount = 1; + fragmentState.targets = &colorTarget; + + // Line strip pipeline + wgpu::RenderPipelineDescriptor pipelineDesc{}; + pipelineDesc.layout = pipelineLayout; + pipelineDesc.vertex.module = shader; + pipelineDesc.vertex.entryPoint.data = "vs_main"; + pipelineDesc.vertex.entryPoint.length = 7; + pipelineDesc.vertex.bufferCount = 1; + pipelineDesc.vertex.buffers = &vertexBufferLayout; + pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::LineStrip; + pipelineDesc.fragment = &fragmentState; + pipelineDesc.multisample.count = 1; + pipelineDesc.multisample.mask = ~0u; + + linePipeline_ = device_.createRenderPipeline(pipelineDesc); + + // Line list pipeline for grid + pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::LineList; + lineListPipeline_ = device_.createRenderPipeline(pipelineDesc); +} + +void Renderer::update() { + time_ += 0.016f; // ~60fps + + for (auto& waterfall : waterfalls_) { + waterfall->update(time_); + } +} + +void Renderer::render() { + wgpu::SurfaceTexture surfaceTexture; + surface_.getCurrentTexture(&surfaceTexture); + + if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal && + surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal) { + std::cerr << "Failed to get surface texture" << std::endl; + return; + } + + wgpu::Texture texture(surfaceTexture.texture); + wgpu::TextureView textureView = texture.createView(); + + wgpu::CommandEncoder encoder = device_.createCommandEncoder(); + + wgpu::RenderPassColorAttachment colorAttachment{}; + colorAttachment.view = textureView; + colorAttachment.loadOp = wgpu::LoadOp::Clear; + colorAttachment.storeOp = wgpu::StoreOp::Store; + colorAttachment.clearValue = {0.1, 0.1, 0.15, 1.0}; + + wgpu::RenderPassDescriptor renderPassDesc{}; + renderPassDesc.colorAttachmentCount = 1; + renderPassDesc.colorAttachments = &colorAttachment; + + wgpu::RenderPassEncoder pass = encoder.beginRenderPass(renderPassDesc); + + for (auto& waterfall : waterfalls_) { + waterfall->render(pass, linePipeline_, lineListPipeline_, width_, height_); + } + + pass.end(); + + wgpu::CommandBuffer commands = encoder.finish(); + device_.getQueue().submit(1, &commands); + surface_.present(); +} + +void Renderer::resize(int width, int height) { + if (width > 0 && height > 0) { + width_ = width; + height_ = height; + configureSurface(); + } +} + +void Renderer::toggleGrid() { + for (auto& waterfall : waterfalls_) { + waterfall->toggleGrid(); + } +} diff --git a/cpp-timeplot/src/renderer.h b/cpp-timeplot/src/renderer.h new file mode 100644 index 0000000..3bb2e21 --- /dev/null +++ b/cpp-timeplot/src/renderer.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +class Waterfall; + +class Renderer { +public: + Renderer(wgpu::Device device, wgpu::Surface surface, int width, int height); + ~Renderer(); + + bool initialize(); + void update(); + void render(); + void resize(int width, int height); + void toggleGrid(); + +private: + void configureSurface(); + void createPipelines(); + + wgpu::Device device_; + wgpu::Surface surface_; + wgpu::TextureFormat surfaceFormat_; + wgpu::RenderPipeline linePipeline_; + wgpu::RenderPipeline lineListPipeline_; + + int width_; + int height_; + float time_; + + std::vector> waterfalls_; +}; diff --git a/cpp-timeplot/src/waterfall.cpp b/cpp-timeplot/src/waterfall.cpp new file mode 100644 index 0000000..9baeba6 --- /dev/null +++ b/cpp-timeplot/src/waterfall.cpp @@ -0,0 +1,164 @@ +#include "waterfall.h" +#include +#include + +Waterfall::Waterfall(wgpu::Device device, float x, float y, float width, float height, const std::string& title) + : device_(device), x_(x), y_(y), width_(width), height_(height), title_(title), showGrid_(true) {} + +Waterfall::~Waterfall() = default; + +bool Waterfall::initialize() { + // Create vertex buffer (large enough for grid, border, and waterfall lines) + wgpu::BufferDescriptor bufferDesc{}; + bufferDesc.size = sizeof(Vertex) * POINTS_PER_LINE * 100; + bufferDesc.usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst; + bufferDesc.mappedAtCreation = false; + + vertexBuffer_ = device_.createBuffer(bufferDesc); + return vertexBuffer_ != nullptr; +} + +void Waterfall::update(float time) { + // Add new line every 10 frames + if (static_cast(time * 60.0f) % 10 == 0 && lines_.size() < MAX_LINES) { + std::vector line; + float phase = time; + float freq = 2.0f + std::sin(time * 0.5f) * 1.0f; + + for (int i = 0; i < POINTS_PER_LINE; ++i) { + float x = (static_cast(i) / POINTS_PER_LINE) * 2.0f - 1.0f; + float y = std::sin(static_cast(i) * 0.1f * freq + phase) * 0.3f; + + float hue = std::fmod(time * 0.1f, 1.0f); + Vertex v{ + {x, y}, + { + std::abs(std::sin(hue * 6.0f)), + std::abs(std::sin((hue + 0.33f) * 6.0f)), + std::abs(std::sin((hue + 0.66f) * 6.0f)) + } + }; + line.push_back(v); + } + lines_.push_back(line); + } + + // Scroll lines down + for (auto& line : lines_) { + for (auto& vertex : line) { + vertex.position[1] -= 0.01f; + } + } + + // Remove lines that scrolled off screen + lines_.erase( + std::remove_if(lines_.begin(), lines_.end(), + [](const std::vector& line) { + return !line.empty() && line[0].position[1] < -1.1f; + }), + lines_.end() + ); +} + +void Waterfall::render(wgpu::RenderPassEncoder& pass, + wgpu::RenderPipeline linePipeline, + wgpu::RenderPipeline lineListPipeline, + int windowWidth, int windowHeight) { + // Set viewport + pass.setViewport( + x_ * windowWidth, + y_ * windowHeight, + width_ * windowWidth, + height_ * windowHeight, + 0.0f, 1.0f + ); + + // Collect all vertices + std::vector allVertices; + + // Border + auto borderVertices = generateBorder(); + size_t borderOffset = allVertices.size(); + allVertices.insert(allVertices.end(), borderVertices.begin(), borderVertices.end()); + + // Grid + size_t gridOffset = allVertices.size(); + size_t gridCount = 0; + if (showGrid_) { + auto gridVertices = generateGridLines(); + gridCount = gridVertices.size(); + allVertices.insert(allVertices.end(), gridVertices.begin(), gridVertices.end()); + } + + // Waterfall lines + size_t linesOffset = allVertices.size(); + for (const auto& line : lines_) { + allVertices.insert(allVertices.end(), line.begin(), line.end()); + } + + // Upload vertices + if (!allVertices.empty()) { + device_.getQueue().writeBuffer(vertexBuffer_, 0, allVertices.data(), + allVertices.size() * sizeof(Vertex)); + } + + // Draw border + pass.setPipeline(lineListPipeline); + pass.setVertexBuffer(0, vertexBuffer_, 0, allVertices.size() * sizeof(Vertex)); + pass.draw(borderVertices.size(), 1, borderOffset, 0); + + // Draw grid + if (showGrid_ && gridCount > 0) { + pass.setPipeline(lineListPipeline); + pass.draw(gridCount, 1, gridOffset, 0); + } + + // Draw waterfall lines + if (!lines_.empty()) { + pass.setPipeline(linePipeline); + for (size_t i = 0; i < lines_.size(); ++i) { + uint32_t start = linesOffset + i * POINTS_PER_LINE; + pass.draw(POINTS_PER_LINE, 1, start, 0); + } + } +} + +void Waterfall::toggleGrid() { + showGrid_ = !showGrid_; +} + +std::vector Waterfall::generateGridLines() { + std::vector vertices; + float gridColor[3] = {0.3f, 0.7f, 0.9f}; + + // Vertical lines + for (int i = 0; i <= 10; ++i) { + float x = -1.0f + (static_cast(i) / 10.0f) * 2.0f; + vertices.push_back({{x, -1.0f}, {gridColor[0], gridColor[1], gridColor[2]}}); + vertices.push_back({{x, 1.0f}, {gridColor[0], gridColor[1], gridColor[2]}}); + } + + // Horizontal lines + for (int i = 0; i <= 10; ++i) { + float y = -1.0f + (static_cast(i) / 10.0f) * 2.0f; + vertices.push_back({{-1.0f, y}, {gridColor[0], gridColor[1], gridColor[2]}}); + vertices.push_back({{1.0f, y}, {gridColor[0], gridColor[1], gridColor[2]}}); + } + + return vertices; +} + +std::vector Waterfall::generateBorder() { + float borderColor[3] = {0.6f, 0.7f, 0.7f}; + + return { + {{-1.0f, 1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{1.0f, 1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{1.0f, 1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{1.0f, -1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{1.0f, -1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{-1.0f, -1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{-1.0f, -1.0f}, {borderColor[0], borderColor[1], borderColor[2]}}, + {{-1.0f, 1.0f}, {borderColor[0], borderColor[1], borderColor[2]}} + }; +} diff --git a/cpp-timeplot/src/waterfall.h b/cpp-timeplot/src/waterfall.h new file mode 100644 index 0000000..4c5d813 --- /dev/null +++ b/cpp-timeplot/src/waterfall.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +struct Vertex { + float position[2]; + float color[3]; +}; + +class Waterfall { +public: + Waterfall(wgpu::Device device, float x, float y, float width, float height, const std::string& title); + ~Waterfall(); + + bool initialize(); + void update(float time); + void render(wgpu::RenderPassEncoder& pass, + wgpu::RenderPipeline linePipeline, + wgpu::RenderPipeline lineListPipeline, + int windowWidth, int windowHeight); + void toggleGrid(); + +private: + std::vector generateGridLines(); + std::vector generateBorder(); + + wgpu::Device device_; + wgpu::Buffer vertexBuffer_; + + float x_, y_, width_, height_; + std::string title_; + bool showGrid_; + + std::vector> lines_; + static constexpr int MAX_LINES = 50; + static constexpr int POINTS_PER_LINE = 100; +}; diff --git a/cpp-timeplot/src/webgpu_impl.cpp b/cpp-timeplot/src/webgpu_impl.cpp new file mode 100644 index 0000000..e0f099b --- /dev/null +++ b/cpp-timeplot/src/webgpu_impl.cpp @@ -0,0 +1,2 @@ +#define WGPU_CPP_IMPLEMENTATION +#include -- cgit v1.2.3