diff options
Diffstat (limited to 'cpp-timeplot/src')
| -rw-r--r-- | cpp-timeplot/src/main.cpp | 143 | ||||
| -rw-r--r-- | cpp-timeplot/src/renderer.cpp | 180 | ||||
| -rw-r--r-- | cpp-timeplot/src/renderer.h | 35 | ||||
| -rw-r--r-- | cpp-timeplot/src/waterfall.cpp | 164 | ||||
| -rw-r--r-- | cpp-timeplot/src/waterfall.h | 39 | ||||
| -rw-r--r-- | cpp-timeplot/src/webgpu_impl.cpp | 2 |
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> |
