diff options
| author | grothedev <grothedev@gmail.com> | 2025-10-02 10:25:04 -0400 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-10-02 10:25:04 -0400 |
| commit | 836459dce3f50767d41978be4a2f7ac788e6a9ba (patch) | |
| tree | 9fd17f4f7e0bb808f8467a14932355e3e72875ef | |
| parent | a162c98ce54159e3e7dbe867d908ce3276b7f633 (diff) | |
added metrics for rust impl. having trouble with c++ atm
| -rw-r--r-- | cpp-timeplot/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | cpp-timeplot/src/main.cpp | 105 | ||||
| -rw-r--r-- | cpp-timeplot/src/renderer.cpp | 185 | ||||
| -rw-r--r-- | cpp-timeplot/src/renderer.h | 14 | ||||
| -rw-r--r-- | cpp-timeplot/src/waterfall.cpp | 44 | ||||
| -rw-r--r-- | cpp-timeplot/src/waterfall.h | 14 | ||||
| -rw-r--r-- | metrics.csv | 1045 | ||||
| -rw-r--r-- | src/graph.rs | 122 | ||||
| -rw-r--r-- | src/main.rs | 640 | ||||
| -rw-r--r-- | src/metrics.rs | 290 | ||||
| -rw-r--r-- | src/renderer.rs | 682 | ||||
| -rw-r--r-- | src/vertex.rs | 27 |
12 files changed, 2436 insertions, 733 deletions
diff --git a/cpp-timeplot/CMakeLists.txt b/cpp-timeplot/CMakeLists.txt index 58993dd..5ae12be 100644 --- a/cpp-timeplot/CMakeLists.txt +++ b/cpp-timeplot/CMakeLists.txt @@ -66,7 +66,6 @@ set(SOURCES src/main.cpp src/renderer.cpp src/waterfall.cpp - src/webgpu_impl.cpp ) add_executable(timeplot ${SOURCES}) diff --git a/cpp-timeplot/src/main.cpp b/cpp-timeplot/src/main.cpp index 7afd3e8..35ae051 100644 --- a/cpp-timeplot/src/main.cpp +++ b/cpp-timeplot/src/main.cpp @@ -1,5 +1,5 @@ #include <GLFW/glfw3.h> -#include <webgpu/webgpu.hpp> +#include <webgpu/webgpu.h> #include <glfw3webgpu.h> #include <iostream> #include <memory> @@ -12,7 +12,8 @@ constexpr int WINDOW_HEIGHT = 720; class Application { public: - Application() : window_(nullptr) {} + Application() : window_(nullptr), instance_(nullptr), device_(nullptr), + surface_(nullptr), adapter_(nullptr) {} ~Application() { cleanup(); @@ -61,9 +62,30 @@ public: } private: + static void onAdapterRequestEnded(WGPURequestAdapterStatus status, WGPUAdapter adapter, + WGPUStringView message, void* userdata1, void* userdata2) { + if (status == WGPURequestAdapterStatus_Success) { + *static_cast<WGPUAdapter*>(userdata1) = adapter; + } else { + std::cerr << "Failed to get adapter: " << std::string(message.data, message.length) << std::endl; + } + } + + static void onDeviceRequestEnded(WGPURequestDeviceStatus status, WGPUDevice device, + WGPUStringView message, void* userdata1, void* userdata2) { + if (status == WGPURequestDeviceStatus_Success) { + *static_cast<WGPUDevice*>(userdata1) = device; + } else { + std::cerr << "Failed to get device: " << std::string(message.data, message.length) << std::endl; + } + } + bool initWebGPU() { // Create instance - instance_ = wgpu::createInstance(wgpu::InstanceDescriptor{}); + WGPUInstanceDescriptor instanceDesc = {}; + instanceDesc.nextInChain = nullptr; + + instance_ = wgpuCreateInstance(&instanceDesc); if (!instance_) { std::cerr << "Failed to create WebGPU instance" << std::endl; return false; @@ -76,23 +98,50 @@ private: return false; } - // Request adapter (synchronous version) - wgpu::RequestAdapterOptions adapterOpts{}; + // Request adapter with callback + WGPURequestAdapterOptions adapterOpts = {}; + adapterOpts.nextInChain = nullptr; adapterOpts.compatibleSurface = surface_; + adapterOpts.powerPreference = WGPUPowerPreference_HighPerformance; - wgpu::Adapter adapter = instance_.requestAdapter(adapterOpts); - if (!adapter) { - std::cerr << "Failed to get adapter" << std::endl; - return false; - } + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + adapterCallbackInfo.callback = onAdapterRequestEnded; + adapterCallbackInfo.userdata1 = &adapter_; + adapterCallbackInfo.userdata2 = nullptr; - // Request device (synchronous version) - wgpu::DeviceDescriptor deviceDesc{}; - device_ = adapter.requestDevice(deviceDesc); + wgpuInstanceRequestAdapter(instance_, &adapterOpts, adapterCallbackInfo); - if (!device_) { - std::cerr << "Failed to get device" << std::endl; - return false; + // Process events until adapter is ready + while (!adapter_) { + wgpuInstanceProcessEvents(instance_); + } + + // Request device with callback + WGPUDeviceDescriptor deviceDesc = {}; + deviceDesc.nextInChain = nullptr; + deviceDesc.label = {nullptr, WGPU_STRLEN}; + deviceDesc.requiredFeatureCount = 0; + deviceDesc.requiredLimits = nullptr; + deviceDesc.defaultQueue.nextInChain = nullptr; + deviceDesc.defaultQueue.label = {nullptr, WGPU_STRLEN}; + deviceDesc.deviceLostCallbackInfo.nextInChain = nullptr; + deviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + deviceDesc.deviceLostCallbackInfo.callback = nullptr; + deviceDesc.uncapturedErrorCallbackInfo.nextInChain = nullptr; + deviceDesc.uncapturedErrorCallbackInfo.callback = nullptr; + + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + deviceCallbackInfo.callback = onDeviceRequestEnded; + deviceCallbackInfo.userdata1 = &device_; + deviceCallbackInfo.userdata2 = nullptr; + + wgpuAdapterRequestDevice(adapter_, &deviceDesc, deviceCallbackInfo); + + // Process events until device is ready + while (!device_) { + wgpuInstanceProcessEvents(instance_); } return true; @@ -101,6 +150,23 @@ private: void cleanup() { renderer_.reset(); + if (device_) { + wgpuDeviceRelease(device_); + device_ = nullptr; + } + if (adapter_) { + wgpuAdapterRelease(adapter_); + adapter_ = nullptr; + } + if (surface_) { + wgpuSurfaceRelease(surface_); + surface_ = nullptr; + } + if (instance_) { + wgpuInstanceRelease(instance_); + instance_ = nullptr; + } + if (window_) { glfwDestroyWindow(window_); window_ = nullptr; @@ -125,9 +191,10 @@ private: } GLFWwindow* window_; - wgpu::Instance instance_; - wgpu::Device device_; - wgpu::Surface surface_; + WGPUInstance instance_; + WGPUAdapter adapter_; + WGPUDevice device_; + WGPUSurface surface_; std::unique_ptr<Renderer> renderer_; }; diff --git a/cpp-timeplot/src/renderer.cpp b/cpp-timeplot/src/renderer.cpp index 1c61698..b65ed46 100644 --- a/cpp-timeplot/src/renderer.cpp +++ b/cpp-timeplot/src/renderer.cpp @@ -4,11 +4,14 @@ #include <sstream> #include <iostream> -Renderer::Renderer(wgpu::Device device, wgpu::Surface surface, int width, int height) +Renderer::Renderer(WGPUDevice device, WGPUSurface surface, int width, int height) : device_(device), surface_(surface), width_(width), height_(height), time_(0.0f), - surfaceFormat_(wgpu::TextureFormat::BGRA8Unorm) {} + surfaceFormat_(WGPUTextureFormat_BGRA8Unorm), linePipeline_(nullptr), lineListPipeline_(nullptr) {} -Renderer::~Renderer() = default; +Renderer::~Renderer() { + if (linePipeline_) wgpuRenderPipelineRelease(linePipeline_); + if (lineListPipeline_) wgpuRenderPipelineRelease(lineListPipeline_); +} bool Renderer::initialize() { configureSurface(); @@ -30,16 +33,19 @@ bool Renderer::initialize() { } void Renderer::configureSurface() { - wgpu::SurfaceConfiguration config{}; + WGPUSurfaceConfiguration config = {}; + config.nextInChain = nullptr; config.device = device_; config.format = surfaceFormat_; - config.usage = wgpu::TextureUsage::RenderAttachment; + config.usage = WGPUTextureUsage_RenderAttachment; config.width = width_; config.height = height_; - config.presentMode = wgpu::PresentMode::Fifo; - config.alphaMode = wgpu::CompositeAlphaMode::Auto; + config.presentMode = WGPUPresentMode_Fifo; + config.alphaMode = WGPUCompositeAlphaMode_Auto; + config.viewFormatCount = 0; + config.viewFormats = nullptr; - surface_.configure(config); + wgpuSurfaceConfigure(surface_, &config); } void Renderer::createPipelines() { @@ -49,74 +55,103 @@ void Renderer::createPipelines() { 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(); + WGPUShaderSourceWGSL wgslSource = {}; + wgslSource.chain.sType = WGPUSType_ShaderSourceWGSL; + wgslSource.chain.next = nullptr; + wgslSource.code = {shaderCode.c_str(), shaderCode.length()}; - wgpu::ShaderModuleDescriptor shaderDesc{}; + WGPUShaderModuleDescriptor shaderDesc = {}; shaderDesc.nextInChain = &wgslSource.chain; - wgpu::ShaderModule shader = device_.createShaderModule(shaderDesc); + shaderDesc.label = {nullptr, WGPU_STRLEN}; + + WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device_, &shaderDesc); // Vertex buffer layout - wgpu::VertexAttribute attributes[2]; - attributes[0].format = wgpu::VertexFormat::Float32x2; + WGPUVertexAttribute attributes[2] = {}; + attributes[0].format = WGPUVertexFormat_Float32x2; attributes[0].offset = 0; attributes[0].shaderLocation = 0; - attributes[1].format = wgpu::VertexFormat::Float32x3; + attributes[1].format = WGPUVertexFormat_Float32x3; attributes[1].offset = 2 * sizeof(float); attributes[1].shaderLocation = 1; - wgpu::VertexBufferLayout vertexBufferLayout{}; + WGPUVertexBufferLayout vertexBufferLayout = {}; vertexBufferLayout.arrayStride = 5 * sizeof(float); - vertexBufferLayout.stepMode = wgpu::VertexStepMode::Vertex; + vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = 2; vertexBufferLayout.attributes = attributes; // Pipeline layout - wgpu::PipelineLayoutDescriptor layoutDesc{}; - wgpu::PipelineLayout pipelineLayout = device_.createPipelineLayout(layoutDesc); + WGPUPipelineLayoutDescriptor layoutDesc = {}; + layoutDesc.nextInChain = nullptr; + layoutDesc.label = {nullptr, WGPU_STRLEN}; + layoutDesc.bindGroupLayoutCount = 0; + layoutDesc.bindGroupLayouts = nullptr; + + WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device_, &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{}; + WGPUBlendState blend = {}; + blend.color.operation = WGPUBlendOperation_Add; + blend.color.srcFactor = WGPUBlendFactor_One; + blend.color.dstFactor = WGPUBlendFactor_Zero; + blend.alpha.operation = WGPUBlendOperation_Add; + blend.alpha.srcFactor = WGPUBlendFactor_One; + blend.alpha.dstFactor = WGPUBlendFactor_Zero; + + WGPUColorTargetState colorTarget = {}; + colorTarget.nextInChain = nullptr; colorTarget.format = surfaceFormat_; colorTarget.blend = &blend; - colorTarget.writeMask = wgpu::ColorWriteMask::All; + colorTarget.writeMask = WGPUColorWriteMask_All; - wgpu::FragmentState fragmentState{}; + WGPUFragmentState fragmentState = {}; + fragmentState.nextInChain = nullptr; fragmentState.module = shader; - fragmentState.entryPoint.data = "fs_main"; - fragmentState.entryPoint.length = 7; + fragmentState.entryPoint = {"fs_main", 7}; + fragmentState.constantCount = 0; + fragmentState.constants = nullptr; fragmentState.targetCount = 1; fragmentState.targets = &colorTarget; // Line strip pipeline - wgpu::RenderPipelineDescriptor pipelineDesc{}; + WGPURenderPipelineDescriptor pipelineDesc = {}; + pipelineDesc.nextInChain = nullptr; + pipelineDesc.label = {nullptr, WGPU_STRLEN}; pipelineDesc.layout = pipelineLayout; + + pipelineDesc.vertex.nextInChain = nullptr; pipelineDesc.vertex.module = shader; - pipelineDesc.vertex.entryPoint.data = "vs_main"; - pipelineDesc.vertex.entryPoint.length = 7; + pipelineDesc.vertex.entryPoint = {"vs_main", 7}; + pipelineDesc.vertex.constantCount = 0; + pipelineDesc.vertex.constants = nullptr; pipelineDesc.vertex.bufferCount = 1; pipelineDesc.vertex.buffers = &vertexBufferLayout; - pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::LineStrip; - pipelineDesc.fragment = &fragmentState; + + pipelineDesc.primitive.nextInChain = nullptr; + pipelineDesc.primitive.topology = WGPUPrimitiveTopology_LineStrip; + pipelineDesc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; + pipelineDesc.primitive.frontFace = WGPUFrontFace_CCW; + pipelineDesc.primitive.cullMode = WGPUCullMode_None; + + pipelineDesc.depthStencil = nullptr; + pipelineDesc.multisample.nextInChain = nullptr; pipelineDesc.multisample.count = 1; pipelineDesc.multisample.mask = ~0u; + pipelineDesc.multisample.alphaToCoverageEnabled = false; - linePipeline_ = device_.createRenderPipeline(pipelineDesc); + pipelineDesc.fragment = &fragmentState; + + linePipeline_ = wgpuDeviceCreateRenderPipeline(device_, &pipelineDesc); // Line list pipeline for grid - pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::LineList; - lineListPipeline_ = device_.createRenderPipeline(pipelineDesc); + pipelineDesc.primitive.topology = WGPUPrimitiveTopology_LineList; + lineListPipeline_ = wgpuDeviceCreateRenderPipeline(device_, &pipelineDesc); + + // Cleanup + wgpuPipelineLayoutRelease(pipelineLayout); + wgpuShaderModuleRelease(shader); } void Renderer::update() { @@ -128,41 +163,75 @@ void Renderer::update() { } void Renderer::render() { - wgpu::SurfaceTexture surfaceTexture; - surface_.getCurrentTexture(&surfaceTexture); + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(surface_, &surfaceTexture); - if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal && - surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal) { + if (surfaceTexture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && + surfaceTexture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) { std::cerr << "Failed to get surface texture" << std::endl; return; } - wgpu::Texture texture(surfaceTexture.texture); - wgpu::TextureView textureView = texture.createView(); + WGPUTextureViewDescriptor viewDesc = {}; + viewDesc.nextInChain = nullptr; + viewDesc.label = {nullptr, WGPU_STRLEN}; + viewDesc.format = surfaceFormat_; + viewDesc.dimension = WGPUTextureViewDimension_2D; + viewDesc.baseMipLevel = 0; + viewDesc.mipLevelCount = 1; + viewDesc.baseArrayLayer = 0; + viewDesc.arrayLayerCount = 1; + viewDesc.aspect = WGPUTextureAspect_All; + + WGPUTextureView textureView = wgpuTextureCreateView(surfaceTexture.texture, &viewDesc); + + WGPUCommandEncoderDescriptor encoderDesc = {}; + encoderDesc.nextInChain = nullptr; + encoderDesc.label = {nullptr, WGPU_STRLEN}; - wgpu::CommandEncoder encoder = device_.createCommandEncoder(); + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, &encoderDesc); - wgpu::RenderPassColorAttachment colorAttachment{}; + WGPURenderPassColorAttachment colorAttachment = {}; + colorAttachment.nextInChain = nullptr; colorAttachment.view = textureView; - colorAttachment.loadOp = wgpu::LoadOp::Clear; - colorAttachment.storeOp = wgpu::StoreOp::Store; + colorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + colorAttachment.resolveTarget = nullptr; + colorAttachment.loadOp = WGPULoadOp_Clear; + colorAttachment.storeOp = WGPUStoreOp_Store; colorAttachment.clearValue = {0.1, 0.1, 0.15, 1.0}; - wgpu::RenderPassDescriptor renderPassDesc{}; + WGPURenderPassDescriptor renderPassDesc = {}; + renderPassDesc.nextInChain = nullptr; + renderPassDesc.label = {nullptr, WGPU_STRLEN}; renderPassDesc.colorAttachmentCount = 1; renderPassDesc.colorAttachments = &colorAttachment; + renderPassDesc.depthStencilAttachment = nullptr; + renderPassDesc.occlusionQuerySet = nullptr; + renderPassDesc.timestampWrites = nullptr; - wgpu::RenderPassEncoder pass = encoder.beginRenderPass(renderPassDesc); + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc); for (auto& waterfall : waterfalls_) { waterfall->render(pass, linePipeline_, lineListPipeline_, width_, height_); } - pass.end(); + wgpuRenderPassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmdBufferDesc = {}; + cmdBufferDesc.nextInChain = nullptr; + cmdBufferDesc.label = {nullptr, WGPU_STRLEN}; + + WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmdBufferDesc); + + WGPUQueue queue = wgpuDeviceGetQueue(device_); + wgpuQueueSubmit(queue, 1, &commands); + wgpuSurfacePresent(surface_); - wgpu::CommandBuffer commands = encoder.finish(); - device_.getQueue().submit(1, &commands); - surface_.present(); + // Cleanup + wgpuCommandBufferRelease(commands); + wgpuCommandEncoderRelease(encoder); + wgpuRenderPassEncoderRelease(pass); + wgpuTextureViewRelease(textureView); } void Renderer::resize(int width, int height) { diff --git a/cpp-timeplot/src/renderer.h b/cpp-timeplot/src/renderer.h index 3bb2e21..0656cdc 100644 --- a/cpp-timeplot/src/renderer.h +++ b/cpp-timeplot/src/renderer.h @@ -1,6 +1,6 @@ #pragma once -#include <webgpu/webgpu.hpp> +#include <webgpu/webgpu.h> #include <vector> #include <memory> @@ -8,7 +8,7 @@ class Waterfall; class Renderer { public: - Renderer(wgpu::Device device, wgpu::Surface surface, int width, int height); + Renderer(WGPUDevice device, WGPUSurface surface, int width, int height); ~Renderer(); bool initialize(); @@ -21,11 +21,11 @@ private: void configureSurface(); void createPipelines(); - wgpu::Device device_; - wgpu::Surface surface_; - wgpu::TextureFormat surfaceFormat_; - wgpu::RenderPipeline linePipeline_; - wgpu::RenderPipeline lineListPipeline_; + WGPUDevice device_; + WGPUSurface surface_; + WGPUTextureFormat surfaceFormat_; + WGPURenderPipeline linePipeline_; + WGPURenderPipeline lineListPipeline_; int width_; int height_; diff --git a/cpp-timeplot/src/waterfall.cpp b/cpp-timeplot/src/waterfall.cpp index 9baeba6..7fa0bee 100644 --- a/cpp-timeplot/src/waterfall.cpp +++ b/cpp-timeplot/src/waterfall.cpp @@ -2,19 +2,24 @@ #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(WGPUDevice device, float x, float y, float width, float height, const std::string& title) + : device_(device), vertexBuffer_(nullptr), x_(x), y_(y), width_(width), height_(height), + title_(title), showGrid_(true) {} -Waterfall::~Waterfall() = default; +Waterfall::~Waterfall() { + if (vertexBuffer_) wgpuBufferRelease(vertexBuffer_); +} bool Waterfall::initialize() { // Create vertex buffer (large enough for grid, border, and waterfall lines) - wgpu::BufferDescriptor bufferDesc{}; + WGPUBufferDescriptor bufferDesc = {}; + bufferDesc.nextInChain = nullptr; + bufferDesc.label = {nullptr, WGPU_STRLEN}; bufferDesc.size = sizeof(Vertex) * POINTS_PER_LINE * 100; - bufferDesc.usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst; + bufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; bufferDesc.mappedAtCreation = false; - vertexBuffer_ = device_.createBuffer(bufferDesc); + vertexBuffer_ = wgpuDeviceCreateBuffer(device_, &bufferDesc); return vertexBuffer_ != nullptr; } @@ -60,12 +65,12 @@ void Waterfall::update(float time) { ); } -void Waterfall::render(wgpu::RenderPassEncoder& pass, - wgpu::RenderPipeline linePipeline, - wgpu::RenderPipeline lineListPipeline, +void Waterfall::render(WGPURenderPassEncoder pass, + WGPURenderPipeline linePipeline, + WGPURenderPipeline lineListPipeline, int windowWidth, int windowHeight) { // Set viewport - pass.setViewport( + wgpuRenderPassEncoderSetViewport(pass, x_ * windowWidth, y_ * windowHeight, width_ * windowWidth, @@ -98,27 +103,28 @@ void Waterfall::render(wgpu::RenderPassEncoder& pass, // Upload vertices if (!allVertices.empty()) { - device_.getQueue().writeBuffer(vertexBuffer_, 0, allVertices.data(), - allVertices.size() * sizeof(Vertex)); + WGPUQueue queue = wgpuDeviceGetQueue(device_); + wgpuQueueWriteBuffer(queue, 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); + wgpuRenderPassEncoderSetPipeline(pass, lineListPipeline); + wgpuRenderPassEncoderSetVertexBuffer(pass, 0, vertexBuffer_, 0, allVertices.size() * sizeof(Vertex)); + wgpuRenderPassEncoderDraw(pass, borderVertices.size(), 1, borderOffset, 0); // Draw grid if (showGrid_ && gridCount > 0) { - pass.setPipeline(lineListPipeline); - pass.draw(gridCount, 1, gridOffset, 0); + wgpuRenderPassEncoderSetPipeline(pass, lineListPipeline); + wgpuRenderPassEncoderDraw(pass, gridCount, 1, gridOffset, 0); } // Draw waterfall lines if (!lines_.empty()) { - pass.setPipeline(linePipeline); + wgpuRenderPassEncoderSetPipeline(pass, 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); + wgpuRenderPassEncoderDraw(pass, POINTS_PER_LINE, 1, start, 0); } } } diff --git a/cpp-timeplot/src/waterfall.h b/cpp-timeplot/src/waterfall.h index 4c5d813..454caf8 100644 --- a/cpp-timeplot/src/waterfall.h +++ b/cpp-timeplot/src/waterfall.h @@ -1,6 +1,6 @@ #pragma once -#include <webgpu/webgpu.hpp> +#include <webgpu/webgpu.h> #include <vector> #include <string> @@ -11,14 +11,14 @@ struct Vertex { class Waterfall { public: - Waterfall(wgpu::Device device, float x, float y, float width, float height, const std::string& title); + Waterfall(WGPUDevice 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, + void render(WGPURenderPassEncoder pass, + WGPURenderPipeline linePipeline, + WGPURenderPipeline lineListPipeline, int windowWidth, int windowHeight); void toggleGrid(); @@ -26,8 +26,8 @@ private: std::vector<Vertex> generateGridLines(); std::vector<Vertex> generateBorder(); - wgpu::Device device_; - wgpu::Buffer vertexBuffer_; + WGPUDevice device_; + WGPUBuffer vertexBuffer_; float x_, y_, width_, height_; std::string title_; diff --git a/metrics.csv b/metrics.csv new file mode 100644 index 0000000..329e70c --- /dev/null +++ b/metrics.csv @@ -0,0 +1,1045 @@ +frame,total_ms,update_ms,render_ms,vertex_count,line_count,fps +0,20.585355999999997,0.020693,20.584712000000003,304,2,48.57822230521542 +1,12.802973,0.002634,12.802714,304,2,78.10685846170261 +2,9.668886,0.003483,9.668671,304,2,103.42453101629287 +3,10.223832999999999,0.002,10.223467,304,2,97.81067433319774 +4,8.824733,0.002637,8.824481,304,2,113.31787601959175 +5,9.340967000000001,0.001766,9.340724999999999,304,2,107.05529737981088 +6,8.676965,0.002006,8.676746000000001,304,2,115.24767012428886 +7,8.564975,0.001603,8.56463,304,2,116.75457313068631 +8,8.774607,0.002961,8.774395,304,2,113.9652180433836 +9,9.376443,0.001887,9.376206999999999,304,2,106.6502510600235 +10,8.823737999999999,0.016567,8.823476,504,4,113.33065419666814 +11,9.399063,0.003183,9.398831999999999,504,4,106.39358412641771 +12,9.544553,0.002565,9.544139,504,4,104.77180020897782 +13,11.734409,0.003143,11.734183999999999,504,4,85.21946013642443 +14,16.307796,0.00265,16.307633,504,4,61.32036481201997 +15,17.068881,0.002518,17.068562,504,4,58.58614867606142 +16,26.337062000000003,0.003478,26.336861,504,4,37.96930728264223 +17,8.962518,0.0038870000000000003,8.962314000000001,504,4,111.57578707233839 +18,15.467921,0.00244,15.467604,504,4,64.64992936025469 +19,15.356398,0.0033079999999999997,15.356159,504,4,65.11943751392742 +20,16.26383,0.017325,16.263552,704,6,61.48613211033318 +21,17.342502999999997,0.003905,17.34211,704,6,57.661803489380986 +22,16.55788,0.004430999999999999,16.557701,704,6,60.39420505523654 +23,27.963865000000002,0.014707,27.963635,704,6,35.76043583388777 +24,8.961756,0.006088,8.961514000000001,704,6,111.58527413600639 +25,12.729468,0.003685,12.729178000000001,704,6,78.55787845964969 +26,15.449618,0.00378,15.449423,704,6,64.7265194518078 +27,17.082518,0.003216,17.082309,704,6,58.5393792648133 +28,16.682744,0.003359,16.682559,704,6,59.94217737801407 +29,17.514577,0.004037,17.514398,704,6,57.09529839059202 +30,18.860458,0.004269,18.860067,704,6,53.02098178103628 +31,13.230066,0.042867,13.229762000000001,904,8,75.58541280141762 +32,18.250951,0.004262999999999999,18.25075,904,8,54.79166537677954 +33,15.342804999999998,0.004948,15.3425,904,8,65.17713025747248 +34,16.938457,0.005633,16.938185,904,8,59.037254692089135 +35,16.541091,0.005096,16.54081,904,8,60.455504416244366 +36,15.82304,0.005094,15.822883999999998,904,8,63.19898072683883 +37,17.354155,0.004086,17.353795,904,8,57.62308795789827 +38,16.542184000000002,0.004856,16.541891,904,8,60.4515099094533 +39,15.877367,0.005339999999999999,15.877196,904,8,62.982735109669 +40,17.704497,0.0058579999999999995,17.704245,904,8,56.48282467443158 +41,15.912214,0.019885999999999997,15.912003,1104,10,62.844805883078244 +42,16.320653,0.021303,16.320396000000002,1104,10,61.272058170711674 +43,17.253076,0.005789,17.252828,1104,10,57.96067901167305 +44,15.502711000000001,0.019284,15.502563,1104,10,64.50484692645047 +45,17.291728,0.004775,17.291441000000003,1104,10,57.831120174918325 +46,16.249458,0.007081,16.24924,1104,10,61.540514151302766 +47,16.904926,0.00589,16.904741,1104,10,59.1543553636378 +48,16.770086,0.006070000000000001,16.7698,1104,10,59.62998639362971 +49,16.641548999999998,0.005116,16.641277,1104,10,60.09056007947338 +50,16.164299,0.0054659999999999995,16.164143,1104,10,61.86473041608547 +51,16.648135,0.005594,16.647529000000002,1104,10,60.066788261868375 +52,17.123464000000002,0.019183,17.123226,1304,12,58.3993986263527 +53,29.310091,0.007357,29.309849999999997,1304,12,34.11794252020575 +54,9.648267,0.010475,9.647891000000001,1304,12,103.64555624341656 +55,9.792821,0.007602,9.792634,1304,12,102.11562122906157 +56,16.905698,0.005496,16.905447,1304,12,59.151654075448405 +57,16.942531,0.006468,16.942237,1304,12,59.02305859732528 +58,15.751287,0.006229999999999999,15.751099000000002,1304,12,63.486875707362834 +59,17.180539000000003,0.0055119999999999995,17.180335000000003,1304,12,58.20539157706285 +60,16.577558,0.006564,16.577278,1304,12,60.322515535762264 +61,16.921288999999998,0.007706,16.921097,1304,12,59.09715270509239 +62,17.084543,0.020509999999999997,17.08419,1504,14,58.53244069800404 +63,15.285663999999999,0.012614,15.285425,1504,14,65.4207759636742 +64,16.839092,0.006849,16.838901,1504,14,59.3856248306025 +65,17.00865,0.006964,17.008344,1504,14,58.79361383766496 +66,16.039631,0.008415,16.039409000000003,1504,14,62.34557391002324 +67,16.656771,0.006919,16.656559,1504,14,60.03564556419729 +68,24.927260999999998,0.017674000000000002,24.92672,1504,14,40.11672200969052 +69,14.515953,0.012724999999999998,14.51567,1504,14,68.88972429161214 +70,10.947745,0.011239,10.947422999999999,1504,14,91.34301173438001 +71,15.840964999999999,0.020725,15.840768000000002,1504,14,63.12746729760467 +72,16.198642999999997,0.033045,16.198447,1704,16,61.7335662005762 +73,16.984214,0.006851,16.984008,1704,16,58.87820301840285 +74,16.745624,0.008107999999999999,16.745376,1704,16,59.71709385090696 +75,16.608743,0.007951000000000001,16.608094,1704,16,60.20925244011542 +76,17.011011999999997,0.007467,17.010751000000003,1704,16,58.785450271859204 +77,16.147173000000002,0.008585,16.146973,1704,16,61.93034533041789 +78,16.240849,0.008481,16.240646,1704,16,61.57313573939392 +79,18.04271,0.008454999999999999,18.0424,1704,16,55.42404660940624 +80,14.616415,0.040323,14.616228,1704,16,68.41622928741418 +81,17.151954,0.007019,17.151752,1704,16,58.302395167337785 +82,16.280562,0.007599,16.280365,1704,16,61.42294105080648 +83,17.768831,0.02155,17.768572,1904,18,56.27832241749613 +84,16.828582,0.010635,16.828198999999998,1904,18,59.42271309608854 +85,16.686888,0.013926000000000001,16.686269,1904,18,59.927291415870954 +86,15.820153999999999,0.010625,15.819939000000002,1904,18,63.21050983448076 +87,16.465339999999998,0.008812,16.465119,1904,18,60.733638054240004 +88,15.942459,0.009027,15.942262,1904,18,62.725580790265795 +89,16.923947000000002,0.0075179999999999995,16.923569,1904,18,59.08787116858732 +90,16.186000999999997,0.009582,16.185807,1904,18,61.78178291228328 +91,16.808137000000002,0.008121999999999999,16.807846,1904,18,59.494993407062296 +92,17.335651000000002,0.008609,17.334981,1904,18,57.68459459641867 +93,24.92418,0.023683,24.924016,2104,20,40.121681034240645 +94,9.247332,0.010159,9.247065000000001,2104,20,108.13929898915708 +95,14.185127000000001,0.010309,14.184929,2104,20,70.4963727148865 +96,16.709347,0.008468,16.709119,2104,20,59.84674326291745 +97,16.524467,0.010083,16.52429,2104,20,60.516324066609826 +98,17.053248,0.008886,17.052992,2104,20,58.63985558645485 +99,15.880458,0.009509,15.880229,2104,20,62.97047604042654 +100,16.650681,0.008638,16.65049,2104,20,60.05760364996483 +101,16.851830000000003,0.008929000000000001,16.851537,2104,20,59.3407362879877 +102,16.728346000000002,0.009907000000000001,16.728073,2104,20,59.77877310763419 +103,16.89492,0.009881000000000001,16.894581,2104,20,59.189389473285466 +104,15.459456999999999,0.02717,15.459242999999999,2304,22,64.68532497616185 +105,17.133239000000003,0.013497,17.132959,2304,22,58.36608010896246 +106,15.923347000000001,0.011646,15.92316,2304,22,62.800867179494354 +107,17.703038,0.008769,17.702665999999997,2304,22,56.487479719582595 +108,16.420952,0.014013,16.42068,2304,22,60.89780909170187 +109,16.176705,0.010179,16.176550000000002,2304,22,61.817286029509724 +110,17.264822,0.010246,17.264556,2204,21,57.921245872097614 +111,15.974644,0.010635,15.974428999999999,2204,21,62.599204088679535 +112,16.75742,0.009398,16.75705,2204,21,59.67505737756767 +113,16.579594,0.010727,16.579402,2204,21,60.31510783677815 +114,16.326130000000003,0.023158,16.325937,2404,23,61.25150295875384 +115,16.880195,0.023155000000000002,16.879890000000003,2404,23,59.24102180099223 +116,24.358542,0.016220000000000002,24.358117,2404,23,41.05336025448485 +117,8.708713,0.013162,8.708499999999999,2404,23,114.82752962464144 +118,16.4451,0.010173000000000001,16.444806999999997,2404,23,60.80838669269266 +119,16.704871,0.010446,16.704597000000003,2404,23,59.862778946332476 +120,16.573409,0.011287,16.573116,2404,23,60.33761672085688 +121,16.315715,0.010779,16.315507999999998,2404,23,61.29060234258811 +122,16.909184,0.01121,16.908901,2404,23,59.13945936125599 +123,16.38371,0.011574,16.38342,2404,23,61.036236603309014 +124,16.715712,0.023607,16.715423,2604,25,59.82395485157916 +125,16.481638,0.025716,16.481462,2704,26,60.67358110886794 +126,16.030023,0.012228000000000001,16.029842,2704,26,62.382942307693504 +127,16.975489,0.011262,16.975272,2704,26,58.90846502271599 +128,16.474492,0.012529,16.474221,2704,26,60.699898971088146 +129,16.281421,0.012054,16.281214,2704,26,61.41970040575696 +130,17.757978,0.010567,17.757604,2704,26,56.312717585301655 +131,15.865929000000001,0.012089,15.865726,2704,26,63.02814036291225 +132,18.875919,0.012122,18.875771999999998,2704,26,52.97755303993411 +133,14.201629,0.011458,14.201347,2704,26,70.4144573837269 +134,17.307956,0.010733000000000001,17.307554999999997,2704,26,57.77689751464586 +135,16.156562,0.026813,16.156326,2904,28,61.89435598984487 +136,15.693735,0.019924,15.693546999999999,2904,28,63.71969451504056 +137,17.091777,0.013058,17.091572,2804,27,58.50766716649767 +138,17.177613,0.013384,17.177323,2804,27,58.21530616622926 +139,15.769389000000002,0.014107999999999999,15.769119000000002,2704,26,63.413997841007024 +140,17.347545,0.013285,17.346987000000002,2704,26,57.64504429877542 +141,16.054378,0.011185,16.054112999999997,2704,26,62.28830540803263 +142,15.50344,0.012133999999999999,15.503243,2704,26,64.50181379100381 +143,17.450364,0.01046,17.450018,2704,26,57.30539489033008 +144,16.446595000000002,0.013156000000000001,16.446318,2604,25,60.80285919365071 +145,15.882684000000001,0.0235,15.882480000000001,2804,27,62.961650562335684 +146,17.206034,0.011547,17.20584,2804,27,58.119145876382674 +147,16.746482,0.012242000000000001,16.746288,2804,27,59.71403426701799 +148,16.244293,0.013365,16.244042,2804,27,61.56008143906294 +149,16.188226999999998,0.013493999999999999,16.187988999999998,2804,27,61.77328746378465 +150,17.853043,0.01218,17.852856,2804,27,56.01286010457713 +151,15.020545,0.036403,15.020316000000001,2704,26,66.5754804502766 +152,17.132082,0.026642,17.131826,2704,26,58.37002181054235 +153,16.827308,0.012309,16.826948,2704,26,59.427212005628 +154,15.493924,0.013345000000000001,15.493729,2704,26,64.5414292725329 +155,17.224239999999998,0.010839999999999999,17.22402,2604,25,58.0577140123454 +156,16.536739,0.024757,16.536518,2804,27,60.471414587845885 +157,16.625395,0.012869,16.625056,2804,27,60.14894683705259 +158,17.57366,0.014149,17.573169999999998,2704,26,56.9033428437787 +159,19.211087000000003,0.015834,19.210912,2704,26,52.05327527796839 +160,17.498132,0.013351,17.497850999999997,2704,26,57.14895738585125 +161,11.764649,0.016998,11.764352,2704,26,85.00041097698707 +162,16.796706,0.012883,16.79643,2704,26,59.535482730959274 +163,16.153149,0.01223,16.152945,2704,26,61.90743365271998 +164,16.902619,0.014602,16.902420000000003,2604,25,59.16242920697674 +165,16.521362,0.011151000000000001,16.521103,2604,25,60.52769741380886 +166,16.721388,0.025198,16.721218,2804,27,59.80364787899186 +167,16.572058000000002,0.013348,16.571874,2804,27,60.342535610242244 +168,16.015502,0.012627,16.015323000000002,2804,27,62.43950392563404 +169,16.588171,0.025137999999999997,16.587975,2704,26,60.2839215969018 +170,17.564225999999998,0.011647999999999999,17.563898000000002,2604,25,56.93390645280925 +171,16.159924,0.020032,16.159713999999997,2604,25,61.88147914557024 +172,15.968157999999999,0.01194,15.967974,2604,25,62.62463084345734 +173,16.749919,0.010557,16.749763,2604,25,59.70178124443468 +174,16.710376999999998,0.010733000000000001,16.710172,2604,25,59.8430544086468 +175,16.415143999999998,0.011744,16.414885,2504,24,60.91935593132781 +176,16.974333,0.011972,16.974062,2504,24,58.912476855497054 +177,16.174113000000002,0.025622,16.173915,2704,26,61.82719262564815 +178,16.14304,0.010693000000000001,16.142844999999998,2704,26,61.94620096338732 +179,17.407024,0.010975,17.406671,2704,26,57.448073835022 +180,16.597614,0.013139000000000001,16.596855,2704,26,60.249623831473606 +181,16.133385,0.013939,16.133166,2604,25,61.98327257423039 +182,17.377981000000002,0.011242,17.377296,2604,25,57.544084091241665 +183,16.046026,0.013313,16.045845999999997,2604,25,62.32072663972998 +184,16.158949999999997,0.012184,16.158699,2504,24,61.88520912559295 +185,17.202598,0.011972,17.202405,2504,24,58.13075443604507 +186,23.157334000000002,0.015174,23.156799,2404,23,43.18286379597927 +187,11.881444,0.045598,11.880989999999999,2604,25,84.16485403626024 +188,14.320544,0.013264,14.320213,2604,25,69.82974948437713 +189,16.554438,0.011255,16.554144,2604,25,60.40676222291569 +190,16.629960999999998,0.012,16.629659,2604,25,60.13243206042396 +191,16.203067,0.011508000000000001,16.202841,2604,25,61.71671079308627 +192,16.549951,0.011131,16.549736999999997,2504,24,60.423139621380145 +193,17.223759,0.010857,17.223413,2504,24,58.05933536343605 +194,15.846513999999999,0.012551999999999999,15.846222000000001,2504,24,63.105361848038 +195,16.625373999999997,0.012143000000000001,16.625118,2504,24,60.14902281296049 +196,16.399562,0.010179,16.399365,2504,24,60.97723829453494 +197,17.130661,0.024531,17.130383,2604,25,58.374863643615384 +198,17.578561,0.01239,17.578259,2504,24,56.887477877170944 +199,16.019407,0.012032,16.019218000000002,2504,24,62.42428324594037 +200,17.15061,0.010185,17.150406999999998,2504,24,58.30696400886033 +201,16.371222,0.010768,16.371004000000003,2504,24,61.08279516336655 +202,16.251497999999998,0.011169,16.25132,2504,24,61.53278916195911 +203,17.416044,0.011798,17.415835,2504,24,57.41832071623154 +204,21.870902,0.017002,21.870534,2404,23,45.72285130261203 +205,10.258562000000001,0.011487,10.258372999999999,2404,23,97.47954927795922 +206,17.169033,0.009212999999999999,17.168692,2404,23,58.24439850514587 +207,16.189246,0.01202,16.188897,2404,23,61.76939926664898 +208,16.435914,0.024579999999999998,16.435727,2604,25,60.842372380386024 +209,16.785602,0.010937,16.785436,2504,24,59.57486660293744 +210,17.015798,0.010244999999999999,17.015563,2504,24,58.76891580400755 +211,16.219726,0.012778000000000001,16.219455,2304,22,61.653322627028345 +212,16.733801,0.010334,16.733495,2304,22,59.75928601039298 +213,16.287557,0.010022,16.287370999999997,2304,22,61.39656180481824 +214,16.664324,0.010079,16.664026,2304,22,60.00843478559346 +215,17.013673999999998,0.009878,17.013476999999998,2304,22,58.776252560146624 +216,17.427027000000002,0.011033,17.426607999999998,2304,22,57.38213408402935 +217,15.42889,0.011117,15.428658,2304,22,64.81347653654929 +218,16.736151,0.022387999999999998,16.735514,2504,24,59.7508949339666 +219,16.369331000000003,0.010786,16.369122,2404,23,61.089851503399856 +220,18.631972,0.010634,18.631714000000002,2404,23,53.67118413445447 +221,17.763777,0.01586,17.763503,2404,23,56.29433425109986 +222,14.089744,0.010672000000000001,14.089433999999999,2304,22,70.97361030832072 +223,15.765758000000002,0.010754000000000001,15.765561000000002,2304,22,63.42860267168885 +224,16.499824999999998,0.009633000000000001,16.499654,2304,22,60.60670340443006 +225,16.801945,0.010059,16.801617,2304,22,59.516919023363066 +226,16.128505,0.017496,16.128286,2304,22,62.0020268462576 +227,17.078118,0.009807,17.077962,2204,21,58.55446132881855 +228,17.06792,0.009269999999999999,17.067736,2204,21,58.58944733746115 +229,16.473662,0.023584,16.473323,2404,23,60.702957241686754 +230,16.188306,0.011833,16.188017,2404,23,61.77298600607129 +231,16.545537,0.009832,16.545355,2404,23,60.43925923951577 +232,16.373352,0.01204,16.373174000000002,2304,22,61.07484893746864 +233,17.231577,0.009948,17.231272,2304,22,58.03299373005732 +234,16.049894,0.011352000000000001,16.049632000000003,2304,22,62.30570743956316 +235,17.136034000000002,0.011424,17.135748999999997,2304,22,58.356560216908996 +236,15.826450999999999,0.010975,15.826251,2204,21,63.18535974995279 +237,18.728991,0.017190999999999998,18.728672,2204,21,53.393159300466316 +238,17.132073000000002,0.010468,17.131856,2204,21,58.37005247409347 +239,13.811800000000002,0.022860000000000002,13.811618,2404,23,72.40185927974629 +240,20.837373,0.009611,20.837035,2404,23,47.99069441239066 +241,14.994335,0.022274000000000002,14.994031,2404,23,66.69185395684437 +242,14.651703,0.009649,14.651276,2404,23,68.25145172544107 +243,16.435368,0.012310000000000001,16.435096,2304,22,60.844393627206884 +244,15.930122,0.010639,15.929957000000002,2304,22,62.774158289559864 +245,16.755418000000002,0.009464,16.755216,2304,22,59.6821875765797 +246,16.652238,0.010244000000000001,16.651989999999998,2204,21,60.051988207230764 +247,17.036893,0.009585999999999999,17.036589000000003,2204,21,58.69614841156777 +248,15.700177,0.010879,15.699991,2204,21,63.6935494421496 +249,17.63319,0.036292,17.63281,2404,23,56.71123602706034 +250,15.818472,0.027061,15.818278000000001,2604,25,63.217231095392776 +251,16.98313,0.010929,16.98292,2604,25,58.881961099043586 +252,16.433941,0.012348,16.43374,2504,24,60.84967689734312 +253,16.590353,0.011186,16.590158000000002,2504,24,60.2759929219107 +254,16.688911,0.010801999999999999,16.688549000000002,2504,24,59.920027136581886 +255,16.246029,0.011775,16.245849,2504,24,61.55350332071917 +256,16.794318999999998,0.010109,16.794052,2504,24,59.54394459221598 +257,16.131937,0.012021,16.131721000000002,2404,23,61.98883618253654 +258,17.152791,0.010367,17.15249,2404,23,58.29955020148033 +259,16.759735000000003,0.01146,16.759351,2404,23,59.666814540922026 +260,15.929094000000001,0.026479,15.928892,2604,25,62.778209482598314 +261,20.796449000000003,0.012646000000000001,20.796181999999998,2504,24,48.08513222617957 +262,12.839808,0.012074,12.839453,2404,23,77.88278454008035 +263,16.631075,0.020912,16.63071,2404,23,60.128404207184445 +264,16.712756,0.012048999999999999,16.712494000000003,2404,23,59.834535967616596 +265,16.416775,0.011243999999999999,16.416588,2404,23,60.91330361779338 +266,15.667494000000001,0.011657,15.667285,2404,23,63.82641665603956 +267,17.771312,0.009885,17.770916,2404,23,56.2704655683272 +268,15.789475000000001,0.012179,15.789285,2304,22,63.33332805555599 +269,17.035398,0.010023,17.035211,2204,21,58.70129949414742 +270,16.039689,0.036163,16.039489,2404,23,62.34534846654446 +271,17.255798,0.009829000000000001,17.255617,2404,23,57.95153605761959 +272,16.3521,0.011908,16.351682,2404,23,61.15422483962305 +273,16.175673000000003,0.011232,16.175462000000003,2404,23,61.82122994202466 +274,17.966505,0.009864999999999999,17.966274000000002,2404,23,55.65912791608607 +275,16.174589,0.011212999999999999,16.174412999999998,2404,23,61.825373120763686 +276,16.853736,0.010908,16.853467000000002,2304,22,59.334025405405654 +277,16.383239,0.011931,16.382880999999998,2304,22,61.03799132760012 +278,16.393234,0.01153,16.392997,2304,22,61.00077629587914 +279,16.868591,0.008771,16.868295,2304,22,59.28177403791461 +280,16.268808,0.010898,16.268468000000002,2204,21,61.46731831858855 +281,16.744401999999997,0.024776000000000003,16.744205,2404,23,59.7214519813846 +282,16.662836,0.011824000000000001,16.662634,2404,23,60.013793570314206 +283,16.45385,0.010594000000000001,16.453566,2304,22,60.77604937446252 +284,16.170507,0.010360000000000001,16.170305,2304,22,61.840980001431 +285,17.421302,0.008653,17.420946,2304,22,57.400991039590494 +286,15.941218,0.01129,15.940875,2304,22,62.73046388299815 +287,16.669878,0.010704,16.669638,2304,22,59.988441427105826 +288,16.623306,0.009588000000000001,16.623135,2304,22,60.15650557115414 +289,16.900098,0.00992,16.899925,2204,21,59.17125450988509 +290,16.222362,0.010740000000000001,16.222075999999998,2204,21,61.64330447070531 +291,17.261822,0.023071,17.261559,2404,23,57.931312233436316 +292,15.817263000000002,0.010801999999999999,15.817049999999998,2404,23,63.22206313443735 +293,17.115222,0.010471,17.114964999999998,2304,22,58.427521419237216 +294,16.273072,0.010332999999999998,16.272774000000002,2304,22,61.45121216203063 +295,16.823049,0.011972,16.822853,2204,21,59.44225687032119 +296,16.964116,0.012518,16.963828,2204,21,58.947958148836044 +297,17.681316,0.010081,17.681097,2204,21,56.55687619631933 +298,19.451801,0.011719,19.451567,2204,21,51.40912144844583 +299,12.649113999999999,0.013231,12.648842,2204,21,79.05692050842455 +300,15.986064,0.010994,15.985857999999999,2104,20,62.55448495639702 +301,16.415319,0.009375,16.415107000000003,2104,20,60.91870648386425 +302,16.822378,0.022758,16.822072000000002,2304,22,59.4446278641462 +303,17.066065,0.011774,17.065592000000002,2304,22,58.595815731394445 +304,15.851565,0.011943,15.850996999999998,2304,22,63.08525372731336 +305,16.677498,0.010636,16.677272,2304,22,59.96103252418318 +306,17.162464999999997,0.011127,17.162174,2104,20,58.26668838071921 +307,15.958704000000001,0.010081999999999999,15.958485000000001,2104,20,62.66172992493626 +308,16.854853,0.009075999999999999,16.854451,2104,20,59.33009323783483 +309,16.851584,0.009995,16.851222,2104,20,59.34160254608707 +310,15.794929999999999,0.011398,15.794737999999999,2104,20,63.311455004865486 +311,16.679672999999998,0.009502,16.679485,2004,19,59.95321371108415 +312,17.474146,0.028665,17.473794,2204,21,57.227403273384574 +313,15.726483,0.011080999999999999,15.726202000000002,2204,21,63.58700797883417 +314,16.84467,0.010572,16.844378,2204,21,59.365959677452864 +315,16.25593,0.008758,16.255644,2204,21,61.51601292574464 +316,17.261304000000003,0.009616000000000001,17.26102,2204,21,57.933050712738726 +317,16.111301,0.010941000000000001,16.111113,2104,20,62.06823396819412 +318,16.358406,0.010981999999999999,16.358200999999998,2104,20,61.13065050470077 +319,16.489928000000003,0.008872999999999999,16.489714,2104,20,60.643078611380226 +320,17.410012,0.008476,17.409696,2104,20,57.438214287273325 +321,20.926238,0.010956,20.925863999999997,2004,19,47.78689796035006 +322,11.638572,0.040799,11.638348,2204,21,85.92119376844514 +323,16.400057999999998,0.010986,16.399848000000002,2104,20,60.97539411141108 +324,17.219527,0.009565,17.219216,2104,20,58.07360446079617 +325,16.09818,0.008698000000000001,16.097939,2104,20,62.11882337009526 +326,16.814686,0.009979,16.814358000000002,2104,20,59.4718212400755 +327,16.578428,0.009413,16.578141000000002,2104,20,60.319349940778466 +328,16.273009000000002,0.009249,16.272709,2104,20,61.45145006679465 +329,16.589443999999997,0.011828,16.589222,2004,19,60.279295677419945 +330,16.729034,0.009641,16.728781,2004,19,59.77631463956616 +331,16.584467,0.018536,16.584187,2004,19,60.29738549933501 +332,16.737119000000003,0.009765000000000001,16.736797000000003,2004,19,59.747439209818594 +333,16.330653,0.022139,16.33035,2204,21,61.23453850865608 +334,16.246507,0.009455,16.246278999999998,2204,21,61.55169231146116 +335,16.986311,0.010431999999999999,16.985996,2204,21,58.87093436591382 +336,16.490142,0.012052,16.489917,1904,18,60.64229161883507 +337,16.426838999999998,0.007500000000000001,16.42661,1904,18,60.87598472231938 +338,16.625892,0.008291999999999999,16.625652000000002,1904,18,60.14714879658788 +339,17.041218,0.008346,17.040966,1904,18,58.68125153964934 +340,16.184154999999997,0.009349,16.183974,1904,18,61.78882987712365 +341,16.535866,0.009571999999999999,16.535598,1904,18,60.47460713578594 +342,16.847775,0.008773999999999999,16.847438999999998,1904,18,59.355018689411516 +343,16.722325,0.022061,16.721989999999998,2104,20,59.800296908474145 +344,16.16356,0.010839999999999999,16.163248000000003,2004,19,61.867558879355784 +345,16.498327,0.008992,16.498113,2004,19,60.61220631643439 +346,16.699261,0.008872000000000001,16.699077,2004,19,59.88288942846034 +347,17.707415,0.008196,17.706968,2004,19,56.47351688544036 +348,16.012144999999997,0.013557000000000001,16.011784000000002,2004,19,62.452594577428584 +349,16.564013999999997,0.010213,16.563601000000002,2004,19,60.37183982095162 +350,16.222353,0.011714,16.222047,1904,18,61.64333866979717 +351,15.958379000000003,0.008019,15.958131,1904,18,62.663006060953926 +352,17.127756,0.00865,17.127487,1704,16,58.38476447235703 +353,16.520538000000002,0.008164,16.5202,1704,16,60.53071637255396 +354,17.419222,0.023195999999999998,17.418928,1904,18,57.40784519538243 +355,15.222595,0.009498,15.222379,1904,18,65.69182192655063 +356,16.696677,0.008053999999999999,16.696445,1904,18,59.89215698429094 +357,17.346093,0.008759000000000001,17.345844999999997,1904,18,57.64986962770233 +358,16.373251,0.009687999999999999,16.372899999999998,1904,18,61.07522568364707 +359,16.426369,0.009455,16.426151,1904,18,60.877726538348185 +360,16.178597999999997,0.008876,16.17839,1904,18,61.810053009537675 +361,16.956170999999998,0.007989,16.955973,1804,17,58.975578861524816 +362,17.243858,0.008681999999999999,17.243398,1804,17,57.991662886576776 +363,15.570627,0.009908,15.570416999999999,1804,17,64.22348952293315 +364,17.198313,0.021233000000000002,17.197989,2004,19,58.145237849782134 +365,16.57453,0.010402,16.57422,2004,19,60.33353585290202 +366,16.030863999999998,0.011172999999999999,16.030647,1904,18,62.379669617308224 +367,17.203733999999997,0.009129,17.203446,1904,18,58.1269159358079 +368,16.306030999999997,0.009697,16.305824,1904,18,61.32700226069729 +369,16.909046,0.0086,16.908734000000003,1904,18,59.13994201683525 +370,15.955555,0.008949,15.95533,1904,18,62.6740968897666 +371,16.672884999999997,0.009758999999999999,16.672687,1804,17,59.97762234910156 +372,17.117505,0.00872,17.117224,1804,17,58.419728809776885 +373,15.701513,0.007928,15.701296,1804,17,63.68812992735159 +374,17.061973000000002,0.007812999999999999,17.061795,1804,17,58.60986885866013 +375,16.602136,0.022263,16.601866,2004,19,60.2332133648345 +376,16.443657,0.009003,16.443345,2004,19,60.81372288414918 +377,16.743472,0.008686,16.743111000000003,2004,19,59.72476915182227 +378,16.25751,0.009969,16.257268,2004,19,61.510034439468285 +379,17.192,0.009340999999999999,17.191693,2004,19,58.16658911121452 +380,16.268146,0.009836000000000001,16.267929,2004,19,61.46981960943797 +381,16.427936,0.009361000000000001,16.427733,1904,18,60.871919637378674 +382,17.48183,0.008362999999999999,17.481532,1804,17,57.20224942125625 +383,15.604814,0.012435,15.604631000000001,1804,17,64.08278881119634 +384,16.590015,0.007768000000000001,16.589831999999998,1804,17,60.277220966949095 +385,16.587358,0.021489,16.587079000000003,2004,19,60.28687630664269 +386,17.663155,0.008777,17.662881,2004,19,56.615027156813156 +387,15.137500999999999,0.010346999999999999,15.13727,2004,19,66.06110215946477 +388,16.819741,0.008478000000000001,16.819543,2004,19,59.45394759645823 +389,17.044681999999998,0.008659,17.044455,2004,19,58.6693257169597 +390,16.127572999999998,0.009807,16.127391000000003,2004,19,62.005609895549696 +391,16.755267,0.009042000000000001,16.754897,2004,19,59.68272543791752 +392,16.12663,0.009528,16.126403,2004,19,62.00923565555854 +393,17.794527000000002,0.008735,17.794234999999997,1904,18,56.19705429652611 +394,16.11147,0.00936,16.111161999999997,1904,18,62.067582908325555 +395,16.333672,0.023183000000000002,16.333468,2104,20,61.223220351186185 +396,16.435031,0.010069,16.434821,2104,20,60.84564124034814 +397,16.785884,0.00933,16.785602,2004,19,59.573865755297724 +398,16.928041,0.008796,16.927738,2004,19,59.07358093000838 +399,15.745060000000002,0.009503000000000001,15.744833,2004,19,63.51198407627534 +400,17.412602999999997,0.00822,17.412216,2004,19,57.42966746557078 +401,16.274444,0.009815,16.274269,2004,19,61.44603158178553 +402,16.725244,0.008713,16.724841,2004,19,59.789860165866635 +403,16.348633,0.020567000000000002,16.348325,2004,19,61.1671936118451 +404,16.564695,0.009928000000000001,16.564342,2004,19,60.36935784208523 +405,16.265439,0.011064,16.265227,1904,18,61.48004981605476 +406,16.997088,0.023557,16.996879999999997,2104,20,58.833607262608744 +407,16.683614,0.009256,16.683404,2104,20,59.939051574796686 +408,15.826930999999998,0.012849,15.826712,2104,20,63.183443461022236 +409,17.364981,0.008832,17.36458,2104,20,57.58716349876801 +410,15.781727999999998,0.011193999999999999,15.781512000000001,2104,20,63.36441738192422 +411,17.645929000000002,0.008454000000000001,17.645609999999998,2104,20,56.67029488784636 +412,21.179023,0.016399,21.178721,2004,19,47.21653118748679 +413,11.32137,0.009085000000000001,11.321062,2004,19,88.3285326775823 +414,17.627494000000002,0.019045000000000003,17.627172,2004,19,56.7295612184012 +415,15.205774,0.011226,15.205592000000001,2004,19,65.76449183053754 +416,16.625205,0.022512,16.624924,2204,21,60.14963424511156 +417,16.963494,0.010487,16.963203,2204,21,58.95011959210761 +418,16.938950000000002,0.011013,16.938603999999998,2104,20,59.03553644116075 +419,15.851008,0.010611,15.850824,2104,20,63.08747052553377 +420,16.762049,0.008957000000000001,16.761851,2104,20,59.65857754025179 +421,16.653259,0.009193,16.65305,2104,20,60.048306460615315 +422,16.860507000000002,0.008794000000000001,16.860127,2104,20,59.31019749287491 +423,17.413815999999997,0.012768999999999999,17.413336,2104,20,57.4256670680338 +424,17.57154,0.012437,17.571299,2104,20,56.91020821168777 +425,14.229442,0.011249,14.229211000000001,2104,20,70.27682462882241 +426,16.801874,0.010512,16.801611,2104,20,59.51717052514499 +427,16.603831,0.024671000000000002,16.603544000000003,2304,22,60.2270644648214 +428,15.843631,0.011319,15.843438,2204,21,63.11684486971453 +429,17.367316000000002,0.008858999999999999,17.36707,2204,21,57.57942102279937 +430,16.171885,0.014735999999999999,16.171650999999997,2204,21,61.83571055569589 +431,16.704084,0.008994,16.703892,2104,20,59.86559933486924 +432,16.83404,0.010655999999999999,16.833730000000003,2104,20,59.4034468255986 +433,16.082133,0.010943,16.081920999999998,2104,20,62.18080648879101 +434,16.616879,0.009398,16.616666,2104,20,60.17977262757946 +435,16.767947,0.008195000000000001,16.767715,2104,20,59.637593081609815 +436,16.955135,0.009767,16.954822,2104,20,58.979182412879645 +437,16.813024,0.024338,16.812706,2304,22,59.4777001448401 +438,16.791955,0.011504,16.791711,2304,22,59.55232729006241 +439,16.706211,0.01056,16.705672,2304,22,59.85797737140995 +440,16.762365000000003,0.012664,16.761891000000002,2304,22,59.65745287135794 +441,16.123876999999997,0.012586,16.123366,2204,21,62.01982314799351 +442,17.207914000000002,0.012957,17.207529,2204,21,58.11279624014857 +443,15.706547,0.011729999999999999,15.706147999999999,2204,21,63.667717672127424 +444,16.710254,0.012527,16.709872,2204,21,59.84349489840191 +445,16.400662999999998,0.012161,16.400296,2204,21,60.97314480518258 +446,16.083607,0.012998,16.083065,2104,20,62.17510785982274 +447,17.059734,0.026587000000000003,17.059293,2304,22,58.617561094446145 +448,16.161890000000003,0.011902000000000001,16.161445999999998,2304,22,61.87395162323217 +449,17.096613,0.01244,17.095942,2304,22,58.49111750964942 +450,16.068586,0.012924,16.068112999999997,2304,22,62.233229482668854 +451,16.445310000000003,0.031445,16.444854,2304,22,60.80761019403099 +452,16.662291999999997,0.01311,16.66191,2304,22,60.015752934830346 +453,16.689338000000003,0.012146,16.688962,2304,22,59.918494070885245 +454,16.818406,0.012324,16.817713,2304,22,59.45866689149971 +455,16.281076,0.013063,16.280648000000003,2204,21,61.421001904296745 +456,17.731195000000003,0.011901,17.730672,2204,21,56.397778040340754 +457,14.912809000000001,0.014481,14.9125,2204,21,67.05644791668692 +458,16.766195,0.02546,16.765787,2404,23,59.64382497042412 +459,21.597234,0.012716,21.596885999999998,2404,23,46.30222555351301 +460,10.908097999999999,0.014004,10.907897,2404,23,91.67501062054998 +461,16.602161000000002,0.009994,16.601993999999998,2304,22,60.233122663971265 +462,16.880924,0.009616999999999999,16.880714,2304,22,59.238463486951304 +463,16.624778,0.011008,16.624461,2304,22,60.151179161610465 +464,16.435239,0.020189,16.435054,2304,22,60.8448711941457 +465,16.465287,0.011472,16.465061,2304,22,60.73383354933321 +466,17.210426000000002,0.023581,17.21014,2304,22,58.104314210467535 +467,16.050044999999997,0.009295,16.049787,2304,22,62.305121262899895 +468,16.682332000000002,0.02634,16.682184,2504,24,59.943657757200846 +469,19.60669,0.012286,19.606391000000002,2404,23,51.002999486399794 +470,13.473270000000001,0.012525,13.472989,2404,23,74.22103171687348 +471,16.505388999999997,0.010694,16.50523,2404,23,60.58627276218695 +472,16.748321999999998,0.0115,16.748017,2404,23,59.70747397858724 +473,16.567176,0.010665000000000001,16.566911,2404,23,60.36031729245829 +474,15.773504,0.011562000000000001,15.773299000000002,2404,23,63.39745436397645 +475,17.812266,0.010793,17.811871,2404,23,56.141088393806825 +476,23.047418999999998,0.016560000000000002,23.047102,2404,23,43.38880635614773 +477,9.435526,0.013088,9.435172,2304,22,105.98243277587281 +478,16.262838000000002,0.011786,16.262621000000003,2304,22,61.48988263918019 +479,16.878418999999997,0.022838999999999998,16.878217,2504,24,59.24725532646157 +480,16.612381,0.009783,16.612064,2504,24,60.19606701772612 +481,16.618584,0.012163,16.618195999999998,2404,23,60.17359842451078 +482,16.429648,0.012745000000000001,16.429271,2404,23,60.86557666968884 +483,16.551360000000003,0.016533,16.551094,2404,23,60.41799586257564 +484,16.263237,0.024556,16.262965,2404,23,61.48837405493138 +485,16.833460000000002,0.010227,16.833159000000002,2404,23,59.40549358242452 +486,17.476576,0.011717,17.476202,2404,23,57.21944618900178 +487,15.826443,0.012317999999999999,15.826171,2404,23,63.18539168908642 +488,16.713896,0.010424000000000001,16.713609,2404,23,59.83045485026352 +489,16.407268,0.023892,16.406995000000002,2604,25,60.94859912082865 +490,16.761927999999997,0.012088,16.761578999999998,2604,25,59.65900820001137 +491,15.953321999999998,0.012605,15.953122,2504,24,62.6828694362215 +492,17.641545,0.019768,17.641265,2504,24,56.68437770047918 +493,15.598185,0.013189000000000001,15.597932,2404,23,64.11002305716978 +494,16.63997,0.028826,16.63976,2404,23,60.09626219278039 +495,16.504209,0.011799,16.503919,2404,23,60.59060449367795 +496,17.316381,0.01196,17.316095999999998,2404,23,57.74878711666138 +497,15.719961000000001,0.010073,15.719758,2404,23,63.613389371640295 +498,17.367621,0.010045,17.367413000000003,2404,23,57.57840984669115 +499,16.088298,0.011652,16.087937,2404,23,62.15697894208572 +500,16.805009,0.039012,16.804794,2604,25,59.506067506420266 +501,15.998809,0.010506000000000001,15.998556,2604,25,62.50465269008462 +502,17.163325999999998,0.011438,17.163085000000002,2604,25,58.26376542635152 +503,17.179857,0.012583,17.179527,2504,24,58.20770219449441 +504,16.215377,0.012128,16.215031,2504,24,61.66985818461082 +505,15.679695,0.013036,15.679495000000001,2504,24,63.776750759501375 +506,16.281052,0.011812,16.280845,2404,23,61.42109244537761 +507,17.251674,0.009817,17.251493999999997,2404,23,57.965389329754316 +508,16.704692,0.011607,16.703929,2404,23,59.8634204090683 +509,16.272361,0.011497,16.272171,2404,23,61.453897194144105 +510,17.215937,0.024471,17.215505999999998,2604,25,58.08571441682204 +511,16.052813,0.016144,16.052545000000002,2604,25,62.29437793862048 +512,16.290245,0.01235,16.289987999999997,2604,25,61.38643095914151 +513,17.234488,0.010752,17.234106,2604,25,58.02319163760479 +514,16.142284,0.013191,16.142079,2504,24,61.94910212210366 +515,16.378529999999998,0.010302,16.378321,2504,24,61.0555403934297 +516,17.04626,0.010703,17.046069,2504,24,58.6638946021004 +517,16.419677,0.010504,16.419396,2504,24,60.90253785138405 +518,16.561283,0.019862,16.560958,2504,24,60.38179529931347 +519,17.021295,0.010168,17.020647999999998,2504,24,58.74993647663119 +520,15.689176999999999,0.027745,15.688959999999998,2704,26,63.738206280673616 +521,17.200229,0.011177,17.199894,2704,26,58.138760826963406 +522,16.391627999999997,0.012456,16.39127,2604,25,61.006752959498606 +523,16.243879,0.012267,16.243675,2504,24,61.561650391510554 +524,17.003652000000002,0.010272,17.003353999999998,2504,24,58.81089544763677 +525,16.784595,0.010417000000000001,16.784339,2504,24,59.57844082624573 +526,16.327261,0.011519999999999999,16.326989,2504,24,61.24726002726361 +527,16.956707,0.011351,16.956442,2504,24,58.97371464872277 +528,16.099683,0.010823,16.099439,2504,24,62.113024212961214 +529,16.368988,0.011654000000000001,16.368785,2504,24,61.09113159591784 +530,16.397034,0.010891,16.396804,2504,24,60.98663941295724 +531,17.61929,0.026438999999999997,17.61892,2604,25,56.75597597860073 +532,16.311215999999998,0.012398999999999999,16.310896,2604,25,61.307507668343064 +533,16.249504,0.011928,16.249275,2604,25,61.54033993899136 +534,16.930441000000002,0.01056,16.930226,2604,25,59.06520686614128 +535,17.02432,0.020564000000000002,17.023964999999997,2604,25,58.73949737786884 +536,15.216504,0.013318,15.216304,2604,25,65.7181176438425 +537,17.451477999999998,0.018033,17.45111,2504,24,57.30173685002497 +538,16.811902,0.01285,16.81158,2504,24,59.48166959336309 +539,16.082847,0.012068,16.08262,2404,23,62.17804596412562 +540,16.178106,0.011867,16.177799999999998,2404,23,61.81193274416672 +541,16.706004,0.037975,16.705765,2604,25,59.85871905693306 +542,16.526843,0.010859,16.526616,2604,25,60.507623869846164 +543,16.950435,0.010767,16.950135000000003,2604,25,58.99553610276079 +544,16.592415,0.011473,16.592221000000002,2604,25,60.2685022041698 +545,16.134545,0.011477000000000001,16.134238,2604,25,61.97881626039037 +546,17.300302,0.010527,17.299918,2504,24,57.802459170943955 +547,15.903283,0.012281,15.903091000000002,2504,24,62.8800984048388 +548,16.889691,0.011119,16.889408000000003,2504,24,59.207714338882816 +549,16.710664,0.011071,16.710362999999997,2504,24,59.842026624435746 +550,16.451578,0.01115,16.451263,2504,24,60.784442683856824 +551,16.331487,0.024539,16.331115999999998,2504,24,61.2314114446529 +552,16.438019,0.033763999999999995,16.437694,2704,26,60.834581101287206 +553,16.55453,0.012548,16.554237,2504,24,60.406426518904496 +554,16.71252,0.011658,16.712310000000002,2504,24,59.835380900067726 +555,16.912957,0.009871999999999999,16.912661,2504,24,59.12626632941833 +556,16.403904999999998,0.012319,16.403623,2504,24,60.96109432479645 +557,16.577041,0.010688,16.576851,2504,24,60.32439685707479 +558,16.613725,0.011061,16.613487,2504,24,60.191197338345255 +559,16.395416,0.011385000000000001,16.395132999999998,2404,23,60.99265794780687 +560,17.189841,0.012008999999999999,17.189388,2404,23,58.173894685820535 +561,15.731499,0.0117,15.730813,2404,23,63.56673321467967 +562,16.752975,0.023424,16.752682,2604,25,59.69089072239409 +563,18.043191,0.011316,18.042915999999998,2604,25,55.42256910099771 +564,15.189848000000001,0.012490000000000001,15.189604000000001,2504,24,65.83344349462878 +565,16.374758999999997,0.011439,16.374572,2504,24,61.06960108542667 +566,17.028771000000003,0.010284,17.028536,2404,23,58.7241439796213 +567,16.432163,0.010761999999999999,16.431881,2404,23,60.85626098037124 +568,16.348532,0.009953,16.348204000000003,2404,23,61.16757149816265 +569,17.445871,0.01061,17.445555000000002,2404,23,57.32015329013954 +570,15.337817,0.013099,15.337610999999999,2304,22,65.19832646327701 +571,17.263277000000002,0.008741,17.263044,2304,22,57.926429611249354 +572,15.909455999999999,0.032705,15.909239,2504,24,62.85570040861234 +573,17.26318,0.01032,17.262835,2504,24,57.92675509378921 +574,16.463345,0.011899000000000002,16.463136,2504,24,60.74099765266414 +575,16.534322,0.011925,16.534106,2404,23,60.48025434608084 +576,16.217202,0.010779,16.216992,2404,23,61.66291817787063 +577,17.270231,0.009737,17.269821,2404,23,57.903105059799145 +578,16.195902,0.012695,16.195618,2404,23,61.744014010457704 +579,16.741279000000002,0.011087000000000001,16.740978,2404,23,59.73259271289845 +580,16.785031,0.010875,16.784797,2304,22,59.57689324493949 +581,16.824946999999998,0.009902,16.824628,2204,21,59.435551268006975 +582,15.640827999999999,0.010244000000000001,15.640533000000001,2204,21,63.935234119319006 +583,16.756191,0.026045,16.755969,2404,23,59.679434305803746 +584,16.773251,0.010628,16.773037000000002,2404,23,59.61873461501292 +585,16.309694999999998,0.010273,16.309465,2404,23,61.31322504804658 +586,17.408959000000003,0.010855,17.408564000000002,2304,22,57.44168850073114 +587,16.412368999999998,0.012021,16.41207,2304,22,60.929656163592234 +588,15.702853,0.011183,15.702631000000002,2304,22,63.682695112792565 +589,17.148359,0.009408999999999999,17.148072,2304,22,58.314617742723954 +590,16.262212,0.010253,16.261979999999998,2304,22,61.49224963983989 +591,17.276329,0.012238,17.275905,2304,22,57.88266708743507 +592,16.676356,0.012405000000000001,16.676046,2304,22,59.965138666984565 +593,16.042072,0.026824,16.04185,2404,23,62.33608725855363 +594,16.181122,0.009816,16.180935,2304,22,61.80041161546153 +595,17.204696,0.00957,17.204367,2304,22,58.12366577125223 +596,17.368381,0.011271,17.367989,2304,22,57.57589034925017 +597,15.699731000000002,0.012331,15.699477,2304,22,63.695358856785504 +598,16.451222,0.010416,16.450941,2304,22,60.78575804277639 +599,16.831479,0.010469,16.831165,2204,21,59.41248537933 +600,16.726772,0.010573,16.726385999999998,2204,21,59.78439832861953 +601,16.824531,0.010981000000000001,16.824101000000002,2204,21,59.43702085960078 +602,15.822955,0.012417,15.822706999999998,2204,21,63.199320228111624 +603,17.738477,0.009668,17.738078,2204,21,56.37462562315807 +604,15.662275999999999,0.029767000000000002,15.661998,2404,23,63.84768088622625 +605,19.47225,0.012174,19.471939,2404,23,51.355133587541246 +606,14.341882,0.015776,14.341651,2204,21,69.7258560626841 +607,18.734648999999997,0.010154,18.734287,2204,21,53.37703417875617 +608,14.960711,0.012209,14.960541000000001,2204,21,66.84174301609062 +609,15.857109000000001,0.01064,15.856940999999999,2204,21,63.063197711512224 +610,16.403237,0.011633000000000001,16.402898999999998,2204,21,60.96357688424547 +611,15.789790000000002,0.011963999999999999,15.789607,2204,21,63.33206458097289 +612,16.708374000000003,0.010797,16.708095,2204,21,59.850228394456565 +613,18.377867,0.009160999999999999,18.377433,2204,21,54.41327875536373 +614,16.116782999999998,0.028357999999999998,16.116437,2304,22,62.047121934941984 +615,18.102933,0.015351,18.102746,2304,22,55.239667516860386 +616,14.601674000000001,0.011594,14.601462000000001,2304,22,68.4852983294929 +617,15.822688000000001,0.009139,15.822374,2304,22,63.20038668524589 +618,17.669213,0.027629,17.668588999999997,2204,21,56.5956163412598 +619,15.819864999999998,0.013169,15.819625,2204,21,63.21166457488734 +620,22.830856,0.011352000000000001,22.830571000000003,2204,21,43.80037261852994 +621,10.083165,0.018695999999999997,10.082927,2204,21,99.17520937126389 +622,16.535092000000002,0.009927,16.534797,2104,20,60.47743792414338 +623,16.250148,0.010366,16.249889,2104,20,61.537901070193335 +624,17.189059,0.01071,17.188843,2104,20,58.17654125220002 +625,16.710875,0.024193,16.710493,2304,22,59.841271028596644 +626,26.457019,0.012256,26.456779,2304,22,37.797153186456875 +627,10.070149,0.021602,10.069922,2304,22,99.30339660316842 +628,12.426627000000002,0.011026000000000001,12.426464999999999,2204,21,80.47235987689982 +629,16.280409,0.010992,16.280258,2204,21,61.423518291217384 +630,16.856185999999997,0.009523,16.855915,2204,21,59.32540136896924 +631,17.333531,0.009174,17.333181,2204,21,57.69164978560917 +632,16.642904,0.013264,16.642466000000002,2104,20,60.08566774163931 +633,16.359980999999998,0.011853,16.359628,2104,20,61.12476536494756 +634,16.137587,0.012483999999999999,16.137235999999998,2104,20,61.96713300445723 +635,16.70388,0.028144,16.703479,2304,22,59.86633045735481 +636,16.606423,0.012931,16.606051,2304,22,60.21766397254846 +637,16.981634,0.012905,16.981171,2304,22,58.88714831564501 +638,15.947649000000002,0.012452999999999999,15.947241000000002,2304,22,62.705167388622606 +639,16.332897,0.012594,16.3325,2204,21,61.226125408125704 +640,16.685069,0.011965,16.684662,2204,21,59.93382466683237 +641,16.931769,0.011817000000000001,16.931407,2204,21,59.06057423769484 +642,15.347174,0.028449000000000002,15.346810000000001,2104,20,65.1585757742761 +643,17.571037,0.011564,17.570666,2104,20,56.91183736053825 +644,16.128266,0.012348,16.127867000000002,2104,20,62.00294563594127 +645,16.511194,0.023684,16.510832,2304,22,60.56497186090842 +646,16.888457,0.012638,16.888067,2304,22,59.212040507904305 +647,15.95408,0.011762,15.953716,2304,22,62.679891287996554 +648,16.443441999999997,0.018264,16.442981,2204,21,60.814518030957274 +649,18.983881,0.013766,18.983465000000002,2204,21,52.676267829533906 +650,13.841759999999999,0.01441,13.841104999999999,2204,21,72.24514801585926 +651,18.451389,0.011208000000000001,18.451192000000002,2204,21,54.19646184902395 +652,14.659221,0.012700999999999999,14.659009000000001,2104,20,68.21644888224279 +653,16.511708,0.011659,16.511476000000002,2104,20,60.56308650807052 +654,16.716732,0.009683,16.716548,2104,20,59.8203045906341 +655,16.516874,0.009574000000000001,16.516704,2104,20,60.54414412799903 +656,16.958329000000003,0.025074000000000003,16.958033,2204,21,58.968074036068046 +657,16.05934,0.009438,16.059052,2204,21,62.26905962511536 +658,16.536500999999998,0.009204,16.536325,2204,21,60.47228491686362 +659,16.668962,0.010258000000000001,16.668771999999997,2204,21,59.9917379378512 +660,16.222328,0.016855000000000002,16.222145,2204,21,61.643433667473616 +661,17.29753,0.008886999999999999,17.297254000000002,2204,21,57.81172225167409 +662,15.8891,0.010402,15.888912000000001,2204,21,62.93622672146314 +663,17.325077,0.009519,17.32473,2204,21,57.719801187608 +664,15.826507999999999,0.011284,15.826317,2004,19,63.18513218456024 +665,16.937337,0.008581,16.937027,2004,19,59.04115859535652 +666,16.087913999999998,0.025026,16.087712,2204,21,62.158462557669075 +667,17.038277,0.009086,17.038059,2204,21,58.69138058971573 +668,17.666534000000002,0.010439,17.666173,2204,21,56.60419865039741 +669,17.211889,0.011673,17.211643,2204,21,58.09937537942523 +670,25.668024000000003,0.023433000000000002,25.667678,2204,21,38.95897868881531 +671,8.676464,0.016115,8.676229000000001,2104,20,115.25432480328392 +672,13.196434,0.008879,13.19616,2104,20,75.77804731187229 +673,17.313829,0.009422999999999999,17.313548,2104,20,57.75729909311222 +674,16.271776000000003,0.009496000000000001,16.271489,2104,20,61.45610657373847 +675,16.410557999999998,0.009755999999999999,16.410363,2104,20,60.936380103589414 +676,17.173023,0.009496000000000001,17.172854,2004,19,58.230865934320356 +677,16.162014999999997,0.022648,16.161804,2204,21,61.873473078697195 +678,20.477668,0.010535,20.477377,2104,20,48.83368555442934 +679,18.289882,0.027563,18.289518,2104,20,54.67503836274067 +680,11.25193,0.01025,11.251671,2104,20,88.87364212184043 +681,16.396461000000002,0.010448,16.396282000000003,2104,20,60.988770686552414 +682,16.391748,0.010180999999999999,16.391563,2104,20,61.00630634389938 +683,16.433640999999998,0.023274,16.433455,2104,20,60.850787722574694 +684,16.326539,0.011835,16.326328,2004,19,61.24996853282867 +685,17.923134,0.008071,17.922912999999998,2004,19,55.793813738155386 +686,15.233117,0.010111,15.232958,2004,19,65.64644648892279 +687,17.460046,0.039487,17.459793,2204,21,57.273617721282065 +688,16.302228,0.009595999999999999,16.301982,2204,21,61.34130868492331 +689,16.403194,0.012454,16.403006,2104,20,60.96373669664579 +690,16.270094,0.009840000000000002,16.269932,2004,19,61.46245989728148 +691,16.413966,0.009007000000000001,16.413731000000002,2004,19,60.923728000898755 +692,17.365807,0.015248999999999999,17.365545,2004,19,57.58442438062337 +693,16.09903,0.009843999999999999,16.098827,2004,19,62.11554360728566 +694,16.589735,0.008757,16.589568,2004,19,60.278238320262496 +695,16.99769,0.009659000000000001,16.997419,1904,18,58.83152357761555 +696,15.856462000000002,0.009491,15.856288999999999,1904,18,63.06577091409167 +697,17.755976999999998,0.021693999999999998,17.755778,2104,20,56.31906371584059 +698,15.350226999999999,0.009826,15.349928,2104,20,65.14561641335989 +699,16.800682000000002,0.008663,16.800524,2104,20,59.52139323867923 +700,16.377389,0.010292,16.377180000000003,2004,19,61.05979408561401 +701,17.96126,0.0084,17.960950999999998,2004,19,55.675381348524546 +702,15.037450000000002,0.010716999999999999,15.037254,2004,19,66.50063674359681 +703,17.627568,0.008107,17.627231000000002,2004,19,56.72932306941037 +704,15.852807,0.01206,15.852533,1904,18,63.08031126601112 +705,16.384278,0.009882,16.384064,1904,18,61.03412063686908 +706,17.126811,0.009602000000000001,17.126614,1804,17,58.387985947880196 +707,16.141099,0.009184999999999999,16.140874999999998,1804,17,61.95365012010644 +708,16.68147,0.039707,16.681207,2004,19,59.946755291949685 +709,16.178766,0.008709000000000001,16.178502,2004,19,61.80941117511682 +710,17.893583,0.00824,17.892858,2004,19,55.88595643477329 +711,16.127848,0.022949999999999998,16.127496,1904,18,62.00455262227174 +712,15.918108,0.016758000000000002,15.917902000000002,1904,18,62.82153632831238 +713,16.737571,0.008783999999999998,16.737395,1904,18,59.74582572345773 +714,16.068397,0.009391,16.068196999999998,1904,18,62.233961483525704 +715,17.755785,0.023819999999999997,17.755546,1904,18,56.31967271511792 +716,15.167446,0.009972,15.167245,1904,18,65.93067811152913 +717,17.706514,0.007837,17.706196000000002,1904,18,56.476390553216746 +718,15.885835,0.025117999999999998,15.885653999999999,2004,19,62.94916194206977 +719,17.169292,0.008772,17.168995,1904,18,58.24351988422121 +720,15.947084,0.009935000000000001,15.946869,1904,18,62.70738901231096 +721,16.926638,0.008074,16.926368,1904,18,59.07847736803965 +722,16.112009999999998,0.009457,16.111793,1904,18,62.06550269022922 +723,17.548332,0.00812,17.547988,1904,18,56.98547303527196 +724,16.069022,0.010642,16.068836,1804,17,62.231540911450615 +725,16.178756999999997,0.008608,16.17858,1804,17,61.809445558765745 +726,16.8353,0.00818,16.835104,1804,17,59.399000908804716 +727,16.817789,0.008261,16.817496000000002,1804,17,59.4608482720291 +728,16.336724,0.007708,16.336477,1804,17,61.21178272951174 +729,16.782576,0.036232999999999994,16.782383000000003,2004,19,59.585608311858685 +730,15.980167999999999,0.010832000000000001,15.979974,2004,19,62.577564891683245 +731,16.861197,0.009099000000000001,16.861015,1904,18,59.307770379528804 +732,17.603088,0.008322,17.602812999999998,1904,18,56.80821455871833 +733,16.20028,0.010051000000000001,16.199848,1804,17,61.72732816963658 +734,15.589943,0.010424999999999999,15.589737,1804,17,64.14391636967498 +735,16.717409999999997,0.007376000000000001,16.717263,1804,17,59.817878487158005 +736,17.177564999999998,0.008656,17.177293,1804,17,58.215468839733695 +737,16.086508,0.007585000000000001,16.086339000000002,1804,17,62.16389535876898 +738,16.374298,0.009535,16.374010000000002,1804,17,61.071320431569035 +739,16.746454,0.023718,16.746146,1904,18,59.7141341086298 +740,16.551462,0.010196,16.551277,1904,18,60.41762353077933 +741,17.031613,0.007781999999999999,17.031228,1904,18,58.714344906733146 +742,16.384664,0.010253,16.384489,1904,18,61.0326827574859 +743,16.032606,0.009297,16.032382000000002,1904,18,62.37289184303537 +744,16.78665,0.008497,16.786474,1904,18,59.57114731051162 +745,16.380981,0.008131999999999999,16.38071,1904,18,61.046404974158754 +746,17.919872,0.014678,17.919528,1904,18,55.80397002835734 +747,16.133122,0.010056,16.132744000000002,1804,17,61.98428301726101 +748,15.401722,0.010041,15.401527,1704,16,64.92780482597985 +749,16.878735,0.007162,16.878529,1704,16,59.246146112253086 +750,16.626864,0.022784000000000002,16.626661000000002,1904,18,60.14363261767222 +751,20.166794,0.008704,20.166467,1904,18,49.58646376811307 +752,17.587721000000002,0.010786,17.587438,1904,18,56.85784986013821 +753,11.775609,0.009007000000000001,11.775402000000001,1904,18,84.92129791333934 +754,16.904685,0.007975999999999999,16.904490000000003,1904,18,59.155198691960244 +755,16.754099,0.007949,16.753811,1904,18,59.68688617633213 +756,16.417398,0.010116,16.417102999999997,1904,18,60.91099210727547 +757,16.410502,0.009592999999999999,16.410317,1804,17,60.93658804587452 +758,16.689624,0.007859999999999999,16.689453,1804,17,59.91746728386452 +759,16.972472,0.006994,16.971908,1804,17,58.91893649905565 +760,16.766775000000003,0.022974,16.766536,2004,19,59.64176175800056 +761,16.175974999999998,0.010013000000000001,16.175629,2004,19,61.82007576050285 +762,16.163376,0.010038,16.163197,2004,19,61.868263164823986 +763,17.152842,0.00796,17.152613000000002,2004,19,58.29937686128048 +764,16.653716,0.008874,16.653360999999997,1904,18,60.046658655641785 +765,16.847102999999997,0.009592999999999999,16.84674,1904,18,59.357386252105194 +766,15.817787,0.008907,15.817583999999998,1904,18,63.21996876048464 +767,16.649303,0.008555,16.649085,1804,17,60.0625743912523 +768,16.549757,0.007226,16.549571,1804,17,60.42384791510836 +769,17.164964,0.021454,17.164561,1804,17,58.258205493469134 +770,16.195795999999998,0.023139999999999997,16.195465,2004,19,61.74441811936876 +771,16.431874999999998,0.009252999999999999,16.431681,2004,19,60.85732760260166 +772,16.813841999999998,0.008347,16.813644,2004,19,59.4748065314281 +773,16.962061000000002,0.008164,16.961775,2004,19,58.9550998549056 +774,16.08018,0.010077000000000001,16.079857,2004,19,62.188358588025764 +775,16.138225,0.022515,16.138023,2004,19,61.96468322879375 +776,20.444244,0.009105,20.443884,2004,19,48.913523043454184 +777,16.744904000000002,0.017282000000000002,16.744629,1904,18,59.7196615758442 +778,13.065836000000001,0.008771,13.065584,1904,18,76.53547771455267 +779,16.277608999999998,0.009498,16.277412,1804,17,61.43408408446229 +780,16.291878999999998,0.01047,16.29166,1804,17,61.380274184457186 +781,17.324139,0.023995,17.323837,2004,19,57.72292637458058 +782,16.046545,0.010125,16.046362000000002,2004,19,62.31871097485472 +783,17.313989,0.009680999999999999,17.313603,2004,19,57.756765353148836 +784,19.983998,0.012660000000000001,19.983777,2004,19,50.04003703363061 +785,12.458796,0.008422,12.458587,2004,19,80.26457773287243 +786,16.397026,0.008185,16.396823,2004,19,60.98666916793326 +787,17.405329000000002,0.009042000000000001,17.404923,2004,19,57.4536683563982 +788,16.112925999999998,0.01071,16.112713,2004,19,62.06197434283507 +789,16.391296,0.00911,16.391099999999998,1904,18,61.007988630063174 +790,16.581211,0.008538,16.580917,1904,18,60.30922590635871 +791,17.180633,0.023609,17.180257,2104,20,58.20507311925003 +792,16.061166,0.010355,16.060855,2104,20,62.261980232319374 +793,16.994169,0.00962,16.99389,2104,20,58.843712805256914 +794,16.405365000000003,0.009075,16.405074,2104,20,60.955669075329915 +795,16.568678,0.011075,16.568455999999998,2004,19,60.35484544995081 +796,17.052473,0.009675,17.05203,2004,19,58.642520647884915 +797,15.662279000000002,0.0099,15.662103,2004,19,63.84766865664952 +798,16.353392000000003,0.008528,16.353201000000002,2004,19,61.14939334909845 +799,17.511200000000002,0.008452,17.510993,2004,19,57.106309105029915 +800,15.893983,0.009473,15.893785000000001,2004,19,62.91689125375307 +801,18.287359,0.009379,18.286977,1904,18,54.682581558113455 +802,18.318597,0.026477,18.318395000000002,2104,20,54.589333451683004 +803,24.679018,0.019813,24.67863,2104,20,40.5202508462857 +804,8.387346,0.013329,8.387156,2104,20,119.22722634788167 +805,13.394547999999999,0.008871,13.394249,2104,20,74.65724114020124 +806,16.478642,0.010350999999999999,16.478374,2104,20,60.68461223928525 +807,17.508713,0.009325,17.508506999999998,2104,20,57.11442068871653 +808,15.931908000000002,0.00956,15.931717,2104,20,62.76712117594452 +809,16.744412999999998,0.010499999999999999,16.744139,2004,19,59.72141274824027 +810,15.960861,0.010665999999999998,15.960598999999998,2004,19,62.65326162542234 +811,17.503922000000003,0.009466,17.503761,2004,19,57.130053481728254 +812,16.504649,0.026862,16.504454000000003,2204,21,60.588989199346194 +813,16.586965,0.008842,16.586743000000002,2204,21,60.28830470191503 +814,16.005221,0.011673,16.004962000000003,2204,21,62.47961212156959 +815,16.451549999999997,0.024046,16.451389,2104,20,60.784546136990144 +816,17.003867999999997,0.009016,17.003291,2104,20,58.81014837329954 +817,16.096762000000002,0.00919,16.096455,2104,20,62.124295557081595 +818,16.875261,0.009687000000000001,16.874972,2104,20,59.25834273022504 +819,16.665902,0.011099,16.665558,2104,20,60.00275292630426 +820,16.273187,0.01083,16.272969000000003,2104,20,61.45077789617977 +821,16.471989999999998,0.008449,16.471766,2104,20,60.70911893462782 +822,16.690112,0.032291,16.6899,2304,22,59.91571536488192 +823,17.153483,0.023798000000000003,17.152810000000002,2204,21,58.29719830077658 +824,16.123216,0.011685000000000001,16.123001000000002,2204,21,62.02236576127245 +825,16.880678,0.009459,16.880262,2204,21,59.23932676163837 +826,16.102014,0.022512,16.101797,2204,21,62.104032452089534 +827,16.865381,0.009332,16.865181,2204,21,59.293057180267674 +828,16.399265,0.011312,16.398989999999998,2204,21,60.9783426269409 +829,16.583562999999998,0.010639,16.583341,2104,20,60.30067241882821 +830,15.925186,0.009186,15.925000000000002,2104,20,62.79361509498225 +831,18.126442,0.008432,18.126238999999998,2104,20,55.16802470115205 +832,15.548002,0.01025,15.547747000000001,2104,20,64.3169456757209 +833,17.102649,0.038378999999999996,17.102341,2304,22,58.470474369204446 +834,16.043093000000002,0.010597,16.042785000000002,2304,22,62.33212012172465 +835,11.497692,0.012923,11.497482,2304,22,86.97397703817427 +836,19.213962000000002,0.010646,19.213608999999998,2304,22,52.04548650611466 +837,17.5739,0.016405999999999997,17.573569,2304,22,56.90256573668907 +838,17.932759,0.016095,17.932256,2304,22,55.76386767925671 +839,15.704971000000002,0.01812,15.704612999999998,2204,21,63.67410675256897 +840,17.023294,0.028534,17.022916,2204,21,58.743037628322696 +841,16.510680999999998,0.020188,16.51012,2204,21,60.56685366278957 +842,12.674558000000001,0.01594,12.674121,2204,21,78.89821483321154 +843,19.740066,0.040803,19.739659,2404,23,50.658391922296516 +844,16.835233000000002,0.023354,16.834818000000002,2304,22,59.39923730191319 +845,15.475666,0.016867999999999998,15.475236,2304,22,64.61757445527708 +846,18.214399,0.016513,18.214056,2304,22,54.90161931777161 +847,11.932228,0.017358,11.931834,2304,22,83.80664533061218 +848,22.355365,0.016676,22.354952,2304,22,44.73199162706581 +849,14.821124,0.021566,14.820805,2304,22,67.4712660119435 +850,16.273978000000003,0.016711,16.2736,2304,22,61.44779106866187 +851,16.628673,0.019613,16.628218,2304,22,60.1370897124503 +852,16.549172000000002,0.019456,16.548683,2204,21,60.42598384982644 +853,16.33401,0.0167,16.333633,2204,21,61.221953457846546 +854,16.542565,0.042666,16.542286,2404,23,60.450117620816364 +855,16.434422,0.016343,16.434106,2404,23,60.84789595886 +856,17.930602,0.016319,17.93006,2404,23,55.77057591262134 +857,14.895296,0.018198000000000002,14.894903,2404,23,67.13528888583349 +858,16.981700999999997,0.016371999999999998,16.981307,2304,22,58.88691598091382 +859,14.499225,0.017243,14.498845,2304,22,68.96920352639538 +860,19.522601,0.012353,19.522246,2304,22,51.22268287919217 +861,17.176083000000002,0.010908,17.175829,2304,22,58.22049183157766 +862,19.643298,0.014085,19.642901000000002,2304,22,50.90794834961013 +863,10.772542,0.021263999999999998,10.772231999999999,2304,22,92.82860071466884 +864,18.23207,0.035431,18.231631,2504,24,54.84840722967825 +865,15.841746,0.01866,15.841309,2404,23,63.12435510580715 +866,17.055128,0.01597,17.054645999999998,2404,23,58.63339166964915 +867,15.607814,0.020285,15.607471,2404,23,64.07047136773926 +868,24.420645,0.014641,24.420288,2404,23,40.94895937433266 +869,10.716723,0.030701000000000003,10.716512,2404,23,93.31210669530229 +870,16.210966,0.010213,16.210667,2404,23,61.686638538381985 +871,16.706644999999998,0.0109,16.706357,2404,23,59.85642239959011 +872,16.663763000000003,0.010379,16.66348,2404,23,60.01045502147383 +873,15.830401,0.010176000000000001,15.830245,2304,22,63.16959374560379 +874,17.376713,0.00937,17.376462,2304,22,57.548283153436444 +875,16.232394,0.025406,16.232212,2504,24,61.605207463544815 +876,16.170129,0.011759,16.16993,2504,24,61.842425623196945 +877,17.357471999999998,0.011164,17.357214,2404,23,57.6120762286121 +878,16.296809999999997,0.009677,16.296514000000002,2404,23,61.36170207543686 +879,18.841174,0.011262,18.840895,2404,23,53.075248920263675 +880,27.763049000000002,0.019542999999999998,27.76253,2404,23,36.01909862277734 +881,8.650534,0.014468,8.65033,2404,23,115.59979996610613 +882,10.390685,0.01027,10.390494,2404,23,96.24004577176578 +883,17.55657,0.009698,17.556332,2404,23,56.95873396682837 +884,16.212384999999998,0.012193,16.212134,2404,23,61.68123937347899 +885,16.052513,0.025239,16.052353,2604,25,62.29554213723421 +886,17.003643999999998,0.01073,17.003438,2604,25,58.81092311742119 +887,16.529512999999998,0.011512999999999999,16.529329,2604,25,60.49785011814929 +888,16.740489,0.010495,16.740254,2604,25,59.7354115521954 +889,18.9866,0.02685,18.986154000000003,2404,23,52.66872425816102 +890,24.737996000000003,0.019697000000000003,24.737709,2404,23,40.42364628080625 +891,8.907959,0.014134,8.907753,2404,23,112.25916060008808 +892,13.888224,0.009935999999999999,13.887932000000001,2404,23,72.00344694901234 +893,16.543924,0.01169,16.543739,2404,23,60.44515194823187 +894,15.781875000000001,0.011559999999999999,15.781678000000001,2404,23,63.363827175161376 +895,17.074473,0.025336,17.074269,2604,25,58.56696133461922 +896,26.203489,0.017617999999999998,26.203071,2604,25,38.162856862305624 +897,9.07014,0.016089000000000003,9.069851,2604,25,110.25188144835691 +898,14.698321,0.012895,14.697959,2604,25,68.03498168260171 +899,16.540525,0.025295,16.540365,2504,24,60.45757314232771 +900,15.732989,0.011814999999999999,15.732838,2504,24,63.56071309780996 +901,17.662354,0.016121999999999997,17.661983,2504,24,56.61759468754844 +902,16.639927999999998,0.012089,16.639526,2504,24,60.0964138787139 +903,16.17397,0.012373,16.173782000000003,2504,24,61.82773926253109 +904,16.417661,0.013198,16.41736,2404,23,60.91001635373029 +905,16.839073,0.012306999999999998,16.83879,2404,23,59.385691837074404 +906,16.354147,0.026874000000000002,16.353919,2604,25,61.14657034695848 +907,17.404041,0.011944,17.403683,2604,25,57.457920261162336 +908,15.235824000000001,0.014671,15.235639,2604,25,65.63478286438593 +909,17.209388,0.024966000000000002,17.208766,2504,24,58.10781882539925 +910,16.249071999999998,0.01079,16.248842999999997,2504,24,61.54197605869431 +911,16.590056,0.012466999999999999,16.589769,2504,24,60.27707200023918 +912,16.200615000000003,0.012618,16.200474,2504,24,61.72605175791165 +913,16.876695,0.010183000000000001,16.876153000000002,2504,24,59.25330759369651 +914,16.703677,0.010978,16.703391,2504,24,59.867058013633766 +915,16.295086,0.012159,16.294744,2504,24,61.36819406783124 +916,17.077445,0.026645,17.077185,2704,26,58.556768884338375 +917,15.990755999999998,0.013054000000000001,15.990579000000002,2704,26,62.536130249251514 +918,16.892509,0.011841,16.892283000000003,2604,25,59.197837337248124 +919,17.033561,0.010716999999999999,17.033289,2604,25,58.70763018960041 +920,16.034983,0.014013,16.034720999999998,2504,24,62.363645786216296 +921,16.415917,0.011157,16.415665999999998,2504,24,60.91648733360433 +922,17.147561,0.011183,17.147343999999997,2504,24,58.31733154353555 +923,16.122391999999998,0.011446,16.122197,2504,24,62.02553566493112 +924,16.565242,0.011448,16.564922,2504,24,60.36736438863977 +925,17.089785,0.011512,17.089012,2504,24,58.514486870373155 +926,16.851462,0.013156000000000001,16.851173,2404,23,59.34203216314406 +927,9.820788,0.046677,9.820056000000001,2604,25,101.82482301827511 +928,8.574152,0.014627000000000001,8.573951999999998,2604,25,116.62960955205833 +929,17.737522,0.016572,17.737185999999998,2604,25,56.37766087055453 +930,15.914416000000001,0.0158,15.914202,2604,25,62.83611035428507 +931,15.543908,0.010727,15.543691,2604,25,64.33388566118636 +932,20.900916,0.015494,20.900637,2604,25,47.844793022468494 +933,14.929153000000001,0.013068,14.928839,2604,25,66.98303647902864 +934,16.10406,0.024045,16.10372,2404,23,62.096142215068745 +935,14.69518,0.011276,14.69491,2404,23,68.04952372138347 +936,15.659873000000001,0.01064,15.659685999999999,2404,23,63.85747828223128 +937,17.431321,0.023793,17.431027,2604,25,57.36799867319292 +938,16.827816,0.01183,16.827475,2604,25,59.42541801027537 +939,18.249145000000002,0.012657999999999999,18.248849,2604,25,54.79708775397422 +940,18.886996,0.025673,18.886620999999998,2604,25,52.9464823310176 +941,12.118475,0.012495,12.118285,2504,24,82.51863373898118 +942,16.448551,0.017447,16.448203000000003,2504,24,60.79562874565669 +943,16.955726000000002,0.012518,16.95535,2504,24,58.97712666505698 +944,16.511688,0.012074,16.511395,2504,24,60.56315986590832 +945,16.326441,0.028034999999999997,16.326148,2504,24,61.25033618778276 +946,16.529489,0.011644999999999999,16.529282,2504,24,60.49793795803366 +947,16.861467,0.026817,16.861171000000002,2604,25,59.30682069359682 +948,16.544857,0.01374,16.544572,2504,24,60.44174331636713 +949,15.897463,0.010753,15.897262999999999,2504,24,62.90311856678012 +950,17.222769,0.02254,17.222494,2504,24,58.06267273282246 +951,16.853375,0.010526,16.853,2504,24,59.335296342720675 +952,16.159794,0.012548,16.159157,2504,24,61.881976960844916 +953,16.584522,0.013524,16.584245,2404,23,60.29718553238978 +954,15.923594,0.011470000000000001,15.923392000000002,2404,23,62.79989303922218 +955,17.00844,0.023745000000000002,17.007932,2404,23,58.79433975132346 +956,9.677705999999999,0.016773999999999997,9.677163,2404,23,103.33027269065624 +957,8.583012,0.016299,8.582823999999999,2404,23,116.5092161120129 +958,18.05115,0.031859,18.050943999999998,2604,25,55.39813252895245 +959,14.889499,0.011106,14.889228,2504,24,67.16142698958507 +960,16.513044,0.011294,16.512854,2504,24,60.558186606903 +961,23.453857000000003,0.011032,23.45355,2504,24,42.63691042373115 +962,17.116778999999998,0.015666,17.115936,2504,24,58.42220665465156 +963,9.781416,0.023585,9.780895,2504,24,102.23468667522167 +964,17.376797,0.014514,17.37647,2304,22,57.54800496317014 +965,17.600955000000003,0.018375,17.600564,2304,22,56.81509895343746 +966,13.463305,0.011862,13.463095,2304,22,74.27596715665284 +967,17.457114,0.009151,17.456819,2304,22,57.283237080310066 +968,28.883163,0.039117,28.882723,2504,24,34.6222468778783 +969,12.520648000000001,0.035494000000000005,12.520283000000001,2504,24,79.86807072605187 +970,8.225767,0.012133999999999999,8.225566,2404,23,121.56921050644883 +971,16.049627,0.009584,16.049277,2404,23,62.306743951121106 +972,16.835514,0.011821000000000002,16.835226,2404,23,59.398245874762125 +973,16.698330000000002,0.009748999999999999,16.698155999999997,2404,23,59.886228143772456 +974,16.301168,0.011341,16.30092,2404,23,61.345297465801224 +975,16.754944000000002,0.011326,16.754654000000002,2304,22,59.683875995049576 +976,15.749144999999999,0.009500999999999999,15.748936000000002,2304,22,63.49551039119902 +977,17.562299,0.009392,17.562066,2204,21,56.94015345029714 +978,9.576795,0.016019000000000002,9.575697,2204,21,104.41906713049616 +979,9.176388000000001,0.030168999999999998,9.176145,2404,23,108.97533975241673 +980,15.08373,0.02408,15.083422,2404,23,66.2965990507653 +981,16.762994,0.012218999999999999,16.762802999999998,2304,22,59.65521433700925 +982,19.235636,0.010589,19.235440999999998,2304,22,51.98684358552013 +983,14.121284,0.018427000000000002,14.121029,2304,22,70.81509018584995 +984,21.944482,0.012248,21.944304000000002,2304,22,45.56954226579602 +985,10.379436,0.010048,10.379235000000001,2304,22,96.34434857539465 +986,17.527433,0.009361000000000001,17.527173,2304,22,57.05342020134951 +987,15.848339999999999,0.011486,15.848145,2204,21,63.09809103035397 +988,17.197993,0.009857000000000001,17.197734,2204,21,58.14631974789151 +989,16.097621999999998,0.041430999999999996,16.097442,2404,23,62.1209766262371 +990,16.985498000000003,0.012322,16.98533,2304,22,58.87375218554085 +991,16.321194000000002,0.009428,16.320997000000002,2304,22,61.27002718060945 +992,16.183177,0.011068,16.182903,2304,22,61.79256396936152 +993,16.720508,0.010205,16.72037,2304,22,59.806795343777836 +994,17.974731000000002,0.010291,17.97431,2204,21,55.633655936214005 +995,14.932739999999999,0.012267,14.93254,2204,21,66.96694645456896 +996,16.689943,0.009026,16.689739,2204,21,59.916322062933354 +997,16.994348,0.009517999999999999,16.994101,2204,21,58.843093009511165 +998,16.89216,0.010227,16.891917,2204,21,59.199060392513445 +999,15.771558,0.010326,15.771288000000002,2204,21,63.40527676466713 +1000,16.985316,0.024999,16.985159,2404,23,58.87438302590308 +1001,17.132959,0.011084,17.132646,2304,22,58.36703397235702 +1002,15.46376,0.012051000000000001,15.463561,2204,21,64.66732541115485 +1003,17.477928,0.008335,17.477574999999998,2204,21,57.215019995505195 +1004,15.543932999999999,0.010876,15.543764,2204,21,64.33378219013167 +1005,17.159707,0.008895,17.159439,2204,21,58.27605331489634 +1006,16.192970000000003,0.009701999999999999,16.19279,2204,21,61.75519376618371 +1007,9.699892,0.015817,9.699316,2204,21,103.09393135511199 +1008,8.459995,0.010825000000000001,8.459729,2204,21,118.2033795528248 +1009,16.46831,0.010067,16.468018,2104,20,60.722684962816466 +1010,16.04561,0.024094,16.045342,2304,22,62.32234237277361 +1011,17.173335,0.010020000000000001,17.172981,2304,22,58.229808013411485 +1012,15.919287,0.01192,15.918986,2304,22,62.81688369585899 +1013,16.708686,0.010648999999999999,16.708384,2304,22,59.849110815775695 +1014,16.230838000000002,0.010727,16.230623,2204,21,61.611113363339584 +1015,17.503227,0.021433999999999998,17.502938999999998,2204,21,57.132321942690915 +1016,16.021271,0.010499000000000001,16.020666,2204,21,62.417020472345804 +1017,16.785307,0.010541,16.784988000000002,2204,21,59.575913624933996 +1018,16.217557,0.010742,16.217276,2104,20,61.66156838542328 +1019,16.179605,0.009269999999999999,16.179413,2104,20,61.80620602295298 +1020,17.093359,0.02489,17.092998,2304,22,58.50225224895821 +1021,16.987164,0.011036,16.986858,2304,22,58.867978198126536 +1022,15.754391,0.011714,15.754204000000001,2304,22,63.47436724148842 +1023,16.998541999999997,0.013949,16.998251999999997,2304,22,58.82857482718225 +1024,16.857528,0.011948,16.857263,2204,21,59.32067857161501 +1025,16.131686,0.011175000000000001,16.131389,2204,21,61.989800694112205 +1026,16.500599,0.010218,16.50039,2204,21,60.603860502276305 +1027,16.79334,0.010395,16.793157,2104,20,59.54741582079562 +1028,9.796621,0.01282,9.796264,2104,20,102.07601171873445 +1029,8.983414,0.011101999999999999,8.983125,2104,20,111.31625459986593 +1030,15.287471,0.010336000000000001,15.2868,2104,20,65.41304313839746 +1031,19.786303,0.028853,19.785997,2304,22,50.540012452048266 +1032,12.686164999999999,0.011777,12.685963999999998,2304,22,78.82602819685856 +1033,17.200736000000003,0.009374,17.200436,2304,22,58.13704715891226 +1034,16.662408,0.009995,16.662169000000002,2204,21,60.01533511842946 +1035,16.132644000000003,0.010539,16.132428,2204,21,61.986119572216424 +1036,16.336244,0.011073999999999999,16.336023,2204,21,61.213581285881865 +1037,16.737871000000002,0.010465,16.737613,2204,21,59.74475487354395 +1038,19.001261,0.010065,19.000937999999998,2104,20,52.628086104390654 +1039,16.408847,0.025485,16.408527999999997,2104,20,60.94273412385404 +1040,14.349423,0.009832,14.349139000000001,2104,20,69.68921328753079 +1041,16.58855,0.023669,16.588337000000003,2304,22,60.28254428506409 +1042,16.383245000000002,0.010243,16.382966999999997,2304,22,61.03796897378998 +1043,17.051349000000002,0.009798,17.050943,2204,21,58.64638627712094 diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 0000000..7426bee --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,122 @@ +use crate::vertex::Vertex; + +#[derive(Clone)] +pub struct SubView { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} + +pub struct GraphView { + pub viewport: SubView, + pub lines: Vec<Vec<Vertex>>, + pub show_grid: bool, + pub title: String, + pub x_axis_label: String, + pub y_axis_label: String, + pub legend_items: Vec<LegendItem>, +} + +#[derive(Clone)] +pub struct LegendItem { + pub label: String, + pub color: [f32; 3], +} + +impl GraphView { + pub fn new(viewport: SubView, title: String, x_axis_label: String, y_axis_label: String) -> Self { + Self { + viewport, + lines: Vec::new(), + show_grid: true, + title, + x_axis_label, + y_axis_label, + legend_items: Vec::new(), + } + } + + pub fn add_legend_item(&mut self, label: String, color: [f32; 3]) { + self.legend_items.push(LegendItem { label, color }); + } + + pub fn generate_grid_lines(&self) -> Vec<Vertex> { + let mut vertices = Vec::new(); + let grid_color = [0.3, 0.7, 0.9]; + + // Vertical grid lines (10 divisions) + for i in 0..=10 { + let x = -1.0 + (i as f32 / 10.0) * 2.0; + vertices.push(Vertex { position: [x, -1.0], color: grid_color }); + vertices.push(Vertex { position: [x, 1.0], color: grid_color }); + } + + // Horizontal grid lines (10 divisions) + for i in 0..=10 { + let y = -1.0 + (i as f32 / 10.0) * 2.0; + vertices.push(Vertex { position: [-1.0, y], color: grid_color }); + vertices.push(Vertex { position: [1.0, y], color: grid_color }); + } + + vertices + } + + pub fn generate_border(&self) -> Vec<Vertex> { + let border_color = [0.6, 0.7, 0.7]; + vec![ + // Top border + Vertex { position: [-1.0, 1.0], color: border_color }, + Vertex { position: [1.0, 1.0], color: border_color }, + // Right border + Vertex { position: [1.0, 1.0], color: border_color }, + Vertex { position: [1.0, -1.0], color: border_color }, + // Bottom border + Vertex { position: [1.0, -1.0], color: border_color }, + Vertex { position: [-1.0, -1.0], color: border_color }, + // Left border + Vertex { position: [-1.0, -1.0], color: border_color }, + Vertex { position: [-1.0, 1.0], color: border_color }, + ] + } + + pub fn update(&mut self, time: f32, graph_idx: usize) { + // Add new line every 10 frames + if (time * 60.0) as u32 % 10 == 0 && self.lines.len() < 50 { + let mut line = Vec::new(); + let phase = time + (graph_idx as f32 * 2.0); + let freq = 2.0 + (time * 0.5 + graph_idx as f32).sin() * 1.0; + + for i in 0..100 { + let x = (i as f32 / 100.0) * 2.0 - 1.0; + let y = ((i as f32) * 0.1 * freq + phase).sin() * 0.3; + + // Different color per graph + let hue = (time * 0.1 + graph_idx as f32 * 0.5) % 1.0; + let color = [ + (hue * 6.0).sin().abs(), + ((hue + 0.33) * 6.0).sin().abs(), + ((hue + 0.66) * 6.0).sin().abs(), + ]; + + line.push(Vertex { + position: [x, y], + color, + }); + } + self.lines.push(line); + } + + // Scroll lines down + for line in self.lines.iter_mut() { + for vertex in line.iter_mut() { + vertex.position[1] -= 0.01; + } + } + + // Remove lines that have scrolled off screen + self.lines.retain(|line| { + line.first().map(|v| v.position[1] > -1.1).unwrap_or(false) + }); + } +} diff --git a/src/main.rs b/src/main.rs index b74ee29..917515d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,631 +1,15 @@ +mod vertex; +mod graph; +mod renderer; +mod metrics; + use winit::{ event::*, event_loop::EventLoop, keyboard::{KeyCode, PhysicalKey}, }; -use wgpu::util::DeviceExt; -use glyphon::{ - Attrs, Buffer, Color as TextColor, Family, FontSystem, Metrics, Resolution, Shaping, - SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport -}; - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -struct Vertex { - position: [f32; 2], - color: [f32; 3], -} - -impl Vertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} - -#[derive(Clone)] -struct SubView { - x: f32, - y: f32, - width: f32, - height: f32, -} - -struct GraphView { - viewport: SubView, - lines: Vec<Vec<Vertex>>, - show_grid: bool, - title: String, -} - -impl GraphView { - fn new(viewport: SubView, title: String) -> Self { - Self { - viewport, - lines: Vec::new(), - show_grid: true, - title, - } - } - - fn generate_grid_lines(&self) -> Vec<Vertex> { - let mut vertices = Vec::new(); - let grid_color = [0.3, 0.7, 0.9]; - - // Vertical grid lines (10 divisions) - for i in 0..=10 { - let x = -1.0 + (i as f32 / 10.0) * 2.0; - vertices.push(Vertex { position: [x, -1.0], color: grid_color }); - vertices.push(Vertex { position: [x, 1.0], color: grid_color }); - } - - // Horizontal grid lines (10 divisions) - for i in 0..=10 { - let y = -1.0 + (i as f32 / 10.0) * 2.0; - vertices.push(Vertex { position: [-1.0, y], color: grid_color }); - vertices.push(Vertex { position: [1.0, y], color: grid_color }); - } - - vertices - } - - fn generate_border(&self) -> Vec<Vertex> { - let border_color = [0.6, 0.7, 0.7]; - vec![ - // Top border - Vertex { position: [-1.0, 1.0], color: border_color }, - Vertex { position: [1.0, 1.0], color: border_color }, - // Right border - Vertex { position: [1.0, 1.0], color: border_color }, - Vertex { position: [1.0, -1.0], color: border_color }, - // Bottom border - Vertex { position: [1.0, -1.0], color: border_color }, - Vertex { position: [-1.0, -1.0], color: border_color }, - // Left border - Vertex { position: [-1.0, -1.0], color: border_color }, - Vertex { position: [-1.0, 1.0], color: border_color }, - ] - } -} - -struct State { - surface: wgpu::Surface<'static>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize<u32>, - window: std::sync::Arc<winit::window::Window>, - line_pipeline: wgpu::RenderPipeline, - line_list_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - time: f32, - graphs: Vec<GraphView>, - font_system: FontSystem, - swash_cache: SwashCache, - text_atlas: TextAtlas, - text_renderer: TextRenderer, -} - -impl State { - async fn new(window: std::sync::Arc<winit::window::Window>) -> Self { - let size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::VULKAN, - ..Default::default() - }); - - let surface = instance.create_surface(window.clone()).unwrap(); - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - label: None, - memory_hints: Default::default(), - }, - None, - ) - .await - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - surface.configure(&device, &config); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Waterfall Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), - }); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Line Strip Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::LineStrip, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let line_list_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Line List Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::LineList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - // Create initial empty buffer (will be updated each frame) - let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Vertex Buffer"), - size: (std::mem::size_of::<Vertex>() * 100 * 100) as u64, // Larger buffer for multiple views - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - // Create 2 graph views side-by-side with header area - // Reserve top 60px for header - let header_height = 60.0 / size.height as f32; - let graph_area_height = 1.0 - header_height; - - let graphs = vec![ - GraphView::new( - SubView { - x: 0.0, - y: header_height, - width: 0.5, - height: graph_area_height - }, - "Frequency vs Time".to_string() - ), - GraphView::new( - SubView { - x: 0.5, - y: header_height, - width: 0.5, - height: graph_area_height - }, - "Position vs Time".to_string() - ), - ]; - - // Initialize text rendering - let mut font_system = FontSystem::new(); - let swash_cache = SwashCache::new(); - let cache = glyphon::Cache::new(&device); - let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format); - let text_renderer = TextRenderer::new( - &mut text_atlas, - &device, - wgpu::MultisampleState::default(), - None, - ); - - Self { - surface, - device, - queue, - config, - size, - window, - line_pipeline, - line_list_pipeline, - vertex_buffer, - time: 0.0, - graphs, - font_system, - swash_cache, - text_atlas, - text_renderer, - } - } - - fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - fn update(&mut self) { - self.time += 0.016; // ~60fps - - // Update each graph independently - for (graph_idx, graph) in self.graphs.iter_mut().enumerate() { - // Add new line every 10 frames - if (self.time * 60.0) as u32 % 10 == 0 && graph.lines.len() < 50 { - let mut line = Vec::new(); - let phase = self.time + (graph_idx as f32 * 2.0); - let freq = 2.0 + (self.time * 0.5 + graph_idx as f32).sin() * 1.0; - - for i in 0..100 { - let x = (i as f32 / 100.0) * 2.0 - 1.0; - let y = ((i as f32) * 0.1 * freq + phase).sin() * 0.3; - // Different color per graph - let hue = (self.time * 0.1 + graph_idx as f32 * 0.5) % 1.0; - let color = [ - (hue * 6.0).sin().abs(), - ((hue + 0.33) * 6.0).sin().abs(), - ((hue + 0.66) * 6.0).sin().abs(), - ]; - - line.push(Vertex { - position: [x, y], - color, - }); - } - graph.lines.push(line); - } - - // Scroll lines down - for line in graph.lines.iter_mut() { - for vertex in line.iter_mut() { - vertex.position[1] -= 0.01; - } - } - - // Remove lines that have scrolled off screen - graph.lines.retain(|line| { - line.first().map(|v| v.position[1] > -1.1).unwrap_or(false) - }); - } - } - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - // Collect all vertex data for all graphs - struct DrawData { - viewport: SubView, - border_offset: usize, - border_count: usize, - grid_offset: usize, - grid_count: usize, - show_grid: bool, - lines_offset: usize, - lines_count: usize, - num_lines: usize, - } - - let mut all_vertices = Vec::new(); - let mut draw_data = Vec::new(); - - for graph in &self.graphs { - let border_vertices = graph.generate_border(); - let border_offset = all_vertices.len(); - all_vertices.extend_from_slice(&border_vertices); - let border_count = border_vertices.len(); - - let grid_offset = all_vertices.len(); - let grid_vertices = graph.generate_grid_lines(); - all_vertices.extend_from_slice(&grid_vertices); - let grid_count = grid_vertices.len(); - - let lines_offset = all_vertices.len(); - let mut line_vertices = Vec::new(); - for line in &graph.lines { - line_vertices.extend_from_slice(line); - } - all_vertices.extend_from_slice(&line_vertices); - let lines_count = line_vertices.len(); - - draw_data.push(DrawData { - viewport: graph.viewport.clone(), - border_offset, - border_count, - grid_offset, - grid_count, - show_grid: graph.show_grid, - lines_offset, - lines_count, - num_lines: graph.lines.len(), - }); - } - - // Write all vertices at once - if !all_vertices.is_empty() { - self.queue.write_buffer( - &self.vertex_buffer, - 0, - bytemuck::cast_slice(&all_vertices), - ); - } - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.1, - b: 0.15, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - // Render each graph view - for data in &draw_data { - // Set viewport for this graph - render_pass.set_viewport( - data.viewport.x * self.size.width as f32, - data.viewport.y * self.size.height as f32, - data.viewport.width * self.size.width as f32, - data.viewport.height * self.size.height as f32, - 0.0, - 1.0, - ); - - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - - // Draw border - render_pass.set_pipeline(&self.line_list_pipeline); - render_pass.draw( - data.border_offset as u32..(data.border_offset + data.border_count) as u32, - 0..1, - ); - - // Draw grid if enabled - if data.show_grid { - render_pass.set_pipeline(&self.line_list_pipeline); - render_pass.draw( - data.grid_offset as u32..(data.grid_offset + data.grid_count) as u32, - 0..1, - ); - } - - // Draw waterfall lines - if data.lines_count > 0 { - render_pass.set_pipeline(&self.line_pipeline); - let points_per_line = 100; - for i in 0..data.num_lines { - let start = (data.lines_offset + i * points_per_line) as u32; - let end = start + points_per_line as u32; - render_pass.draw(start..end, 0..1); - } - } - } - } - - // Render text (header and labels) - let mut text_areas = Vec::new(); - - // Main header - let mut header_buffer = Buffer::new(&mut self.font_system, Metrics::new(32.0, 40.0)); - header_buffer.set_size(&mut self.font_system, Some(800.0), Some(50.0)); - header_buffer.set_text( - &mut self.font_system, - "TimePlot - Waterfall Display", - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - - // Graph titles - create all buffers first - let mut graph_buffers = Vec::new(); - for graph in &self.graphs { - let x_offset = graph.viewport.x * self.size.width as f32; - let y_offset = graph.viewport.y * self.size.height as f32; - let width = graph.viewport.width * self.size.width as f32; - - let mut title_buffer = Buffer::new(&mut self.font_system, Metrics::new(18.0, 24.0)); - title_buffer.set_size(&mut self.font_system, Some(width), Some(30.0)); - title_buffer.set_text( - &mut self.font_system, - &graph.title, - Attrs::new().family(Family::SansSerif), - Shaping::Advanced, - ); - graph_buffers.push(title_buffer); - } - - // Now create text areas with references to the buffers - text_areas.push(TextArea { - buffer: &header_buffer, - left: 10.0, - top: 15.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: 800, - bottom: 50, - }, - default_color: TextColor::rgb(255, 255, 255), - custom_glyphs: &[] - }); - - for (i, graph) in self.graphs.iter().enumerate() { - let x_offset = graph.viewport.x * self.size.width as f32; - let y_offset = graph.viewport.y * self.size.height as f32; - let width = graph.viewport.width * self.size.width as f32; - - text_areas.push(TextArea { - buffer: &graph_buffers[i], - left: x_offset + 10.0, - top: y_offset + 5.0, - scale: 1.0, - bounds: TextBounds { - left: 0, - top: 0, - right: width as i32, - bottom: 30, - }, - default_color: TextColor::rgb(230, 230, 230), - custom_glyphs: &[] - }); - } - - self.text_renderer - .prepare( - &self.device, - &self.queue, - &mut self.font_system, - &mut self.text_atlas, - &Viewport::new( - &self.device, - &glyphon::Cache::new(&self.device), - ), - text_areas, - &mut self.swash_cache, - ) - .expect("Failed to prepare text"); - - { - let mut text_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Text Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - self.text_renderer - .render(&self.text_atlas, &Viewport::new( - &self.device, - &glyphon::Cache::new(&self.device), - ), &mut text_pass) - .expect("Failed to render text"); - } - - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } -} +use renderer::State; fn main() { env_logger::init(); @@ -667,6 +51,18 @@ fn main() { println!("Grid now: {}", graph.show_grid); } } + KeyCode::KeyM => { + // Toggle metrics display + state.toggle_metrics(); + } + KeyCode::KeyE => { + // Export metrics to CSV + if let Err(e) = state.export_metrics("metrics.csv") { + eprintln!("Failed to export metrics: {}", e); + } else { + println!("Metrics exported to metrics.csv"); + } + } KeyCode::Escape => control_flow.exit(), _ => {} } diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..369aff8 --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,290 @@ +use std::time::{Duration, Instant}; +use std::collections::VecDeque; + +/// Rolling average calculator for smooth metric display +pub struct RollingAverage { + values: VecDeque<f64>, + capacity: usize, + sum: f64, +} + +impl RollingAverage { + pub fn new(capacity: usize) -> Self { + Self { + values: VecDeque::with_capacity(capacity), + capacity, + sum: 0.0, + } + } + + pub fn push(&mut self, value: f64) { + if self.values.len() >= self.capacity { + if let Some(old) = self.values.pop_front() { + self.sum -= old; + } + } + self.values.push_back(value); + self.sum += value; + } + + pub fn average(&self) -> f64 { + if self.values.is_empty() { + 0.0 + } else { + self.sum / self.values.len() as f64 + } + } + + pub fn min(&self) -> f64 { + self.values.iter().copied().fold(f64::INFINITY, f64::min) + } + + pub fn max(&self) -> f64 { + self.values.iter().copied().fold(f64::NEG_INFINITY, f64::max) + } + + pub fn latest(&self) -> f64 { + self.values.back().copied().unwrap_or(0.0) + } +} + +/// Frame timing breakdown +#[derive(Debug, Clone)] +pub struct FrameTiming { + pub total_ms: f64, + pub update_ms: f64, + pub render_ms: f64, + pub vertex_count: usize, + pub line_count: usize, +} + +/// Performance metrics collector with rolling averages +pub struct PerformanceMetrics { + // Rolling averages (default: 60 frames) + frame_time: RollingAverage, + update_time: RollingAverage, + render_time: RollingAverage, + vertex_count: RollingAverage, + line_count: RollingAverage, + + // Session-wide statistics + pub total_frames: u64, + session_start: Instant, + + // Current frame timing + frame_start: Option<Instant>, + update_start: Option<Instant>, + render_start: Option<Instant>, + + // Historical data for export + history: VecDeque<FrameTiming>, + history_capacity: usize, +} + +impl PerformanceMetrics { + pub fn new(rolling_window: usize, history_capacity: usize) -> Self { + Self { + frame_time: RollingAverage::new(rolling_window), + update_time: RollingAverage::new(rolling_window), + render_time: RollingAverage::new(rolling_window), + vertex_count: RollingAverage::new(rolling_window), + line_count: RollingAverage::new(rolling_window), + total_frames: 0, + session_start: Instant::now(), + frame_start: None, + update_start: None, + render_start: None, + history: VecDeque::with_capacity(history_capacity), + history_capacity, + } + } + + // Frame timing markers + pub fn begin_frame(&mut self) { + self.frame_start = Some(Instant::now()); + } + + pub fn begin_update(&mut self) { + self.update_start = Some(Instant::now()); + } + + pub fn end_update(&mut self) -> f64 { + if let Some(start) = self.update_start.take() { + let duration = start.elapsed(); + duration.as_secs_f64() * 1000.0 + } else { + 0.0 + } + } + + pub fn begin_render(&mut self) { + self.render_start = Some(Instant::now()); + } + + pub fn end_render(&mut self) -> f64 { + if let Some(start) = self.render_start.take() { + let duration = start.elapsed(); + duration.as_secs_f64() * 1000.0 + } else { + 0.0 + } + } + + pub fn end_frame(&mut self, update_ms: f64, render_ms: f64, vertex_count: usize, line_count: usize) { + if let Some(start) = self.frame_start.take() { + let total_ms = start.elapsed().as_secs_f64() * 1000.0; + + // Update rolling averages + self.frame_time.push(total_ms); + self.update_time.push(update_ms); + self.render_time.push(render_ms); + self.vertex_count.push(vertex_count as f64); + self.line_count.push(line_count as f64); + + // Record to history + let timing = FrameTiming { + total_ms, + update_ms, + render_ms, + vertex_count, + line_count, + }; + + if self.history.len() >= self.history_capacity { + self.history.pop_front(); + } + self.history.push_back(timing); + + self.total_frames += 1; + } + } + + // Getters for current metrics + pub fn fps(&self) -> f64 { + let avg_frame_time = self.frame_time.average(); + if avg_frame_time > 0.0 { + 1000.0 / avg_frame_time + } else { + 0.0 + } + } + + pub fn avg_frame_time_ms(&self) -> f64 { + self.frame_time.average() + } + + pub fn avg_update_time_ms(&self) -> f64 { + self.update_time.average() + } + + pub fn avg_render_time_ms(&self) -> f64 { + self.render_time.average() + } + + pub fn avg_vertex_count(&self) -> f64 { + self.vertex_count.average() + } + + pub fn avg_line_count(&self) -> f64 { + self.line_count.average() + } + + pub fn min_fps(&self) -> f64 { + let max_frame_time = self.frame_time.max(); + if max_frame_time > 0.0 && max_frame_time.is_finite() { + 1000.0 / max_frame_time + } else { + 0.0 + } + } + + pub fn max_fps(&self) -> f64 { + let min_frame_time = self.frame_time.min(); + if min_frame_time > 0.0 && min_frame_time.is_finite() { + 1000.0 / min_frame_time + } else { + 0.0 + } + } + + pub fn session_duration(&self) -> Duration { + self.session_start.elapsed() + } + + // Export functionality + pub fn export_to_csv(&self, path: &str) -> std::io::Result<()> { + use std::io::Write; + let mut file = std::fs::File::create(path)?; + + // Write header + writeln!(file, "frame,total_ms,update_ms,render_ms,vertex_count,line_count,fps")?; + + // Write data + for (i, timing) in self.history.iter().enumerate() { + let fps = if timing.total_ms > 0.0 { + 1000.0 / timing.total_ms + } else { + 0.0 + }; + writeln!( + file, + "{},{},{},{},{},{},{}", + i, + timing.total_ms, + timing.update_ms, + timing.render_ms, + timing.vertex_count, + timing.line_count, + fps + )?; + } + + Ok(()) + } + + pub fn format_summary(&self) -> String { + format!( + "FPS: {:.1} (min: {:.1}, max: {:.1}) | Frame: {:.2}ms | Update: {:.2}ms | Render: {:.2}ms | Vertices: {:.0} | Lines: {:.0}", + self.fps(), + self.min_fps(), + self.max_fps(), + self.avg_frame_time_ms(), + self.avg_update_time_ms(), + self.avg_render_time_ms(), + self.avg_vertex_count(), + self.avg_line_count() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rolling_average() { + let mut avg = RollingAverage::new(3); + avg.push(10.0); + avg.push(20.0); + avg.push(30.0); + assert_eq!(avg.average(), 20.0); + + avg.push(40.0); + assert_eq!(avg.average(), 30.0); // (20 + 30 + 40) / 3 + } + + #[test] + fn test_metrics_lifecycle() { + let mut metrics = PerformanceMetrics::new(60, 1000); + + metrics.begin_frame(); + metrics.begin_update(); + let update_ms = metrics.end_update(); + metrics.begin_render(); + let render_ms = metrics.end_render(); + metrics.end_frame(update_ms, render_ms, 1000, 10); + + assert_eq!(metrics.total_frames, 1); + assert!(metrics.fps() > 0.0); + } +} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..2b9c0d0 --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,682 @@ +use glyphon::{ + Attrs, Buffer, Color as TextColor, Family, FontSystem, Metrics, Resolution, Shaping, + SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport +}; +use crate::vertex::Vertex; +use crate::graph::{GraphView, SubView}; +use crate::metrics::PerformanceMetrics; + +pub struct State { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + pub size: winit::dpi::PhysicalSize<u32>, + pub window: std::sync::Arc<winit::window::Window>, + line_pipeline: wgpu::RenderPipeline, + line_list_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + time: f32, + pub graphs: Vec<GraphView>, + font_system: FontSystem, + swash_cache: SwashCache, + text_atlas: TextAtlas, + text_renderer: TextRenderer, + viewport: Viewport, + metrics: PerformanceMetrics, + show_metrics: bool, +} + +impl State { + pub async fn new(window: std::sync::Arc<winit::window::Window>) -> Self { + let size = window.inner_size(); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::VULKAN, + ..Default::default() + }); + + let surface = instance.create_surface(window.clone()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + label: None, + memory_hints: Default::default(), + }, + None, + ) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &config); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Waterfall Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Line Strip Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::LineStrip, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + let line_list_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Line List Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::LineList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + // Create initial empty buffer (will be updated each frame) + let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Vertex Buffer"), + size: (std::mem::size_of::<Vertex>() * 100 * 100) as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + // Create 2 graph views side-by-side with header area + // Reserve top 60px for header + let header_height = 60.0 / size.height as f32; + let graph_area_height = 1.0 - header_height; + + let mut graphs = vec![ + GraphView::new( + SubView { + x: 0.0, + y: header_height, + width: 0.5, + height: graph_area_height + }, + "Frequency vs Time".to_string(), + "Time (s)".to_string(), + "Frequency (Hz)".to_string() + ), + GraphView::new( + SubView { + x: 0.5, + y: header_height, + width: 0.5, + height: graph_area_height + }, + "Position vs Time".to_string(), + "Time (s)".to_string(), + "Position (m)".to_string() + ), + ]; + + // Add legend items + graphs[0].add_legend_item("Signal A".to_string(), [1.0, 0.3, 0.3]); + graphs[0].add_legend_item("Signal B".to_string(), [0.3, 1.0, 0.3]); + graphs[1].add_legend_item("Object 1".to_string(), [0.3, 0.6, 1.0]); + graphs[1].add_legend_item("Object 2".to_string(), [1.0, 0.8, 0.3]); + + // Initialize text rendering + let font_system = FontSystem::new(); + let swash_cache = SwashCache::new(); + let cache = glyphon::Cache::new(&device); + let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format); + let text_renderer = TextRenderer::new( + &mut text_atlas, + &device, + wgpu::MultisampleState::default(), + None, + ); + + let mut viewport = Viewport::new(&device, &cache); + + // Initialize viewport with current resolution + viewport.update( + &queue, + Resolution { + width: size.width, + height: size.height, + }, + ); + + Self { + surface, + device, + queue, + config, + size, + window, + line_pipeline, + line_list_pipeline, + vertex_buffer, + time: 0.0, + graphs, + font_system, + swash_cache, + text_atlas, + text_renderer, + viewport, + metrics: PerformanceMetrics::new(60, 10000), // 60 frame rolling avg, 10k frame history + show_metrics: true, // Show metrics by default + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + + // Update text viewport resolution + self.viewport.update( + &self.queue, + Resolution { + width: new_size.width, + height: new_size.height, + }, + ); + } + } + + pub fn update(&mut self) { + self.metrics.begin_update(); + + self.time += 0.016; // ~60fps + + // Update each graph independently + for (graph_idx, graph) in self.graphs.iter_mut().enumerate() { + graph.update(self.time, graph_idx); + } + } + + pub fn toggle_metrics(&mut self) { + self.show_metrics = !self.show_metrics; + println!("Metrics display: {}", if self.show_metrics { "ON" } else { "OFF" }); + } + + pub fn export_metrics(&self, path: &str) -> std::io::Result<()> { + self.metrics.export_to_csv(path) + } + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + self.metrics.begin_frame(); + + // End update timing from previous update() call + let update_ms = self.metrics.end_update(); + + self.metrics.begin_render(); + + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + // Collect all vertex data for all graphs + struct DrawData { + viewport: SubView, + border_offset: usize, + border_count: usize, + grid_offset: usize, + grid_count: usize, + show_grid: bool, + lines_offset: usize, + lines_count: usize, + num_lines: usize, + } + + let mut all_vertices = Vec::new(); + let mut draw_data = Vec::new(); + let mut total_line_count = 0; + + for graph in &self.graphs { + total_line_count += graph.lines.len(); + let border_vertices = graph.generate_border(); + let border_offset = all_vertices.len(); + all_vertices.extend_from_slice(&border_vertices); + let border_count = border_vertices.len(); + + let grid_offset = all_vertices.len(); + let grid_vertices = graph.generate_grid_lines(); + all_vertices.extend_from_slice(&grid_vertices); + let grid_count = grid_vertices.len(); + + let lines_offset = all_vertices.len(); + let mut line_vertices = Vec::new(); + for line in &graph.lines { + line_vertices.extend_from_slice(line); + } + all_vertices.extend_from_slice(&line_vertices); + let lines_count = line_vertices.len(); + + draw_data.push(DrawData { + viewport: graph.viewport.clone(), + border_offset, + border_count, + grid_offset, + grid_count, + show_grid: graph.show_grid, + lines_offset, + lines_count, + num_lines: graph.lines.len(), + }); + } + + // Write all vertices at once + if !all_vertices.is_empty() { + self.queue.write_buffer( + &self.vertex_buffer, + 0, + bytemuck::cast_slice(&all_vertices), + ); + } + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.1, + b: 0.15, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + // Render each graph view + for data in &draw_data { + // Set viewport for this graph + render_pass.set_viewport( + data.viewport.x * self.size.width as f32, + data.viewport.y * self.size.height as f32, + data.viewport.width * self.size.width as f32, + data.viewport.height * self.size.height as f32, + 0.0, + 1.0, + ); + + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + + // Draw border + render_pass.set_pipeline(&self.line_list_pipeline); + render_pass.draw( + data.border_offset as u32..(data.border_offset + data.border_count) as u32, + 0..1, + ); + + // Draw grid if enabled + if data.show_grid { + render_pass.set_pipeline(&self.line_list_pipeline); + render_pass.draw( + data.grid_offset as u32..(data.grid_offset + data.grid_count) as u32, + 0..1, + ); + } + + // Draw waterfall lines + if data.lines_count > 0 { + render_pass.set_pipeline(&self.line_pipeline); + let points_per_line = 100; + for i in 0..data.num_lines { + let start = (data.lines_offset + i * points_per_line) as u32; + let end = start + points_per_line as u32; + render_pass.draw(start..end, 0..1); + } + } + } + } + + // Render text (header and labels) + self.viewport.update( + &self.queue, + Resolution { + width: self.size.width, + height: self.size.height, + }, + ); + + let mut text_areas = Vec::new(); + + // Main header + let mut header_buffer = Buffer::new(&mut self.font_system, Metrics::new(24.0, 32.0)); + header_buffer.set_size(&mut self.font_system, Some(500.0), Some(40.0)); + header_buffer.set_text( + &mut self.font_system, + "TimePlot - Waterfall Display", + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + + // Performance metrics (if enabled) + let mut metrics_buffer = Buffer::new(&mut self.font_system, Metrics::new(11.0, 14.0)); + if self.show_metrics { + let metrics_text = self.metrics.format_summary(); + metrics_buffer.set_size(&mut self.font_system, Some(self.size.width as f32 - 520.0), Some(40.0)); + metrics_buffer.set_text( + &mut self.font_system, + &metrics_text, + Attrs::new().family(Family::Monospace), + Shaping::Advanced, + ); + } + + // Graph titles and labels - create all buffers first + let mut graph_buffers = Vec::new(); + let mut x_axis_buffers = Vec::new(); + let mut y_axis_buffers = Vec::new(); + let mut legend_buffers = Vec::new(); + + for graph in &self.graphs { + let width = graph.viewport.width * self.size.width as f32; + let height = graph.viewport.height * self.size.height as f32; + + // Title + let mut title_buffer = Buffer::new(&mut self.font_system, Metrics::new(18.0, 24.0)); + title_buffer.set_size(&mut self.font_system, Some(width), Some(30.0)); + title_buffer.set_text( + &mut self.font_system, + &graph.title, + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + graph_buffers.push(title_buffer); + + // X-axis label + let mut x_buffer = Buffer::new(&mut self.font_system, Metrics::new(14.0, 18.0)); + x_buffer.set_size(&mut self.font_system, Some(width), Some(20.0)); + x_buffer.set_text( + &mut self.font_system, + &graph.x_axis_label, + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + x_axis_buffers.push(x_buffer); + + // Y-axis label + let mut y_buffer = Buffer::new(&mut self.font_system, Metrics::new(14.0, 18.0)); + y_buffer.set_size(&mut self.font_system, Some(height * 0.5), Some(20.0)); + y_buffer.set_text( + &mut self.font_system, + &graph.y_axis_label, + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + y_axis_buffers.push(y_buffer); + + // Legend + let mut legend_text = String::new(); + for (i, item) in graph.legend_items.iter().enumerate() { + if i > 0 { legend_text.push_str(" "); } + legend_text.push_str(&format!("● {}", item.label)); + } + let mut legend_buffer = Buffer::new(&mut self.font_system, Metrics::new(12.0, 16.0)); + legend_buffer.set_size(&mut self.font_system, Some(width * 0.8), Some(20.0)); + legend_buffer.set_text( + &mut self.font_system, + &legend_text, + Attrs::new().family(Family::SansSerif), + Shaping::Advanced, + ); + legend_buffers.push(legend_buffer); + } + + // Now create text areas with references to the buffers + text_areas.push(TextArea { + buffer: &header_buffer, + left: 10.0, + top: 15.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: 500, + bottom: 40, + }, + default_color: TextColor::rgb(255, 255, 255), + custom_glyphs: &[] + }); + + // Metrics display + if self.show_metrics { + text_areas.push(TextArea { + buffer: &metrics_buffer, + left: 520.0, + top: 18.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: (self.size.width as i32 - 520).max(0), + bottom: 40, + }, + default_color: TextColor::rgb(100, 255, 100), + custom_glyphs: &[] + }); + } + + for (i, graph) in self.graphs.iter().enumerate() { + let x_offset = graph.viewport.x * self.size.width as f32; + let y_offset = graph.viewport.y * self.size.height as f32; + let width = graph.viewport.width * self.size.width as f32; + let height = graph.viewport.height * self.size.height as f32; + + // Graph title + text_areas.push(TextArea { + buffer: &graph_buffers[i], + left: x_offset + 10.0, + top: y_offset + 5.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: width as i32, + bottom: 30, + }, + default_color: TextColor::rgb(230, 230, 230), + custom_glyphs: &[] + }); + + // X-axis label (bottom center) + text_areas.push(TextArea { + buffer: &x_axis_buffers[i], + left: x_offset + width * 0.5 - 50.0, + top: y_offset + height - 20.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: 200, + bottom: 20, + }, + default_color: TextColor::rgb(200, 200, 200), + custom_glyphs: &[] + }); + + // Y-axis label (left center, rotated appearance via positioning) + text_areas.push(TextArea { + buffer: &y_axis_buffers[i], + left: x_offset + 5.0, + top: y_offset + height * 0.3, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: 150, + bottom: 20, + }, + default_color: TextColor::rgb(200, 200, 200), + custom_glyphs: &[] + }); + + // Legend (top right) + text_areas.push(TextArea { + buffer: &legend_buffers[i], + left: x_offset + width * 0.3, + top: y_offset + 30.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: (width * 0.7) as i32, + bottom: 20, + }, + default_color: TextColor::rgb(220, 220, 220), + custom_glyphs: &[] + }); + } + + self.text_renderer + .prepare( + &self.device, + &self.queue, + &mut self.font_system, + &mut self.text_atlas, + &self.viewport, + text_areas, + &mut self.swash_cache, + ) + .expect("Failed to prepare text"); + + { + let mut text_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Text Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + self.text_renderer + .render(&self.text_atlas, &self.viewport, &mut text_pass) + .expect("Failed to render text"); + } + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + // Finalize metrics + let render_ms = self.metrics.end_render(); + let vertex_count = all_vertices.len(); + self.metrics.end_frame(update_ms, render_ms, vertex_count, total_line_count); + + Ok(()) + } +} diff --git a/src/vertex.rs b/src/vertex.rs new file mode 100644 index 0000000..8a25c30 --- /dev/null +++ b/src/vertex.rs @@ -0,0 +1,27 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Vertex { + pub position: [f32; 2], + pub color: [f32; 3], +} + +impl Vertex { + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} |
