summaryrefslogtreecommitdiff
path: root/cpp-timeplot/src
diff options
context:
space:
mode:
Diffstat (limited to 'cpp-timeplot/src')
-rw-r--r--cpp-timeplot/src/main.cpp143
-rw-r--r--cpp-timeplot/src/renderer.cpp180
-rw-r--r--cpp-timeplot/src/renderer.h35
-rw-r--r--cpp-timeplot/src/waterfall.cpp164
-rw-r--r--cpp-timeplot/src/waterfall.h39
-rw-r--r--cpp-timeplot/src/webgpu_impl.cpp2
6 files changed, 563 insertions, 0 deletions
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 <GLFW/glfw3.h>
+#include <webgpu/webgpu.hpp>
+#include <glfw3webgpu.h>
+#include <iostream>
+#include <memory>
+#include <cstdlib>
+
+#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<Renderer>(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<Application*>(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<Application*>(glfwGetWindowUserPointer(window));
+ app->renderer_->resize(width, height);
+ }
+
+ GLFWwindow* window_;
+ wgpu::Instance instance_;
+ wgpu::Device device_;
+ wgpu::Surface surface_;
+ std::unique_ptr<Renderer> 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 <fstream>
+#include <sstream>
+#include <iostream>
+
+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<Waterfall>(
+ device_, 0.0f, 0.1f, 0.5f, 0.9f, "Frequency vs Time"));
+ waterfalls_.push_back(std::make_unique<Waterfall>(
+ 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 <webgpu/webgpu.hpp>
+#include <vector>
+#include <memory>
+
+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<std::unique_ptr<Waterfall>> 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 <cmath>
+#include <algorithm>
+
+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<int>(time * 60.0f) % 10 == 0 && lines_.size() < MAX_LINES) {
+ std::vector<Vertex> 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<float>(i) / POINTS_PER_LINE) * 2.0f - 1.0f;
+ float y = std::sin(static_cast<float>(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<Vertex>& 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<Vertex> 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<Vertex> Waterfall::generateGridLines() {
+ std::vector<Vertex> 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<float>(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<float>(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<Vertex> 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 <webgpu/webgpu.hpp>
+#include <vector>
+#include <string>
+
+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<Vertex> generateGridLines();
+ std::vector<Vertex> generateBorder();
+
+ wgpu::Device device_;
+ wgpu::Buffer vertexBuffer_;
+
+ float x_, y_, width_, height_;
+ std::string title_;
+ bool showGrid_;
+
+ std::vector<std::vector<Vertex>> 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 <webgpu/webgpu.hpp>