summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgrothedev <grothedev@gmail.com>2025-10-02 10:25:04 -0400
committergrothedev <grothedev@gmail.com>2025-10-02 10:25:04 -0400
commit836459dce3f50767d41978be4a2f7ac788e6a9ba (patch)
tree9fd17f4f7e0bb808f8467a14932355e3e72875ef
parenta162c98ce54159e3e7dbe867d908ce3276b7f633 (diff)
added metrics for rust impl. having trouble with c++ atm
-rw-r--r--cpp-timeplot/CMakeLists.txt1
-rw-r--r--cpp-timeplot/src/main.cpp105
-rw-r--r--cpp-timeplot/src/renderer.cpp185
-rw-r--r--cpp-timeplot/src/renderer.h14
-rw-r--r--cpp-timeplot/src/waterfall.cpp44
-rw-r--r--cpp-timeplot/src/waterfall.h14
-rw-r--r--metrics.csv1045
-rw-r--r--src/graph.rs122
-rw-r--r--src/main.rs640
-rw-r--r--src/metrics.rs290
-rw-r--r--src/renderer.rs682
-rw-r--r--src/vertex.rs27
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,
+ },
+ ],
+ }
+ }
+}