Compute Buffer Animation¶
This example shows a compute pass updating point positions.
Preview¶
Run And Adapt¶
Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.
| Route | Availability | Command or action |
|---|---|---|
| C | Canonical native source | just example-c features/compute_buffer_animation (build and run), or rerun ./build/examples/c/features/compute_buffer_animation |
| Python | No verified adaptation on this page | Start from the C source. |
| Browser | Live WebGPU route | Open live example |
Use this example as capability or integration evidence, not as a minimal copy-paste template. Start from the nearest supported, copy-safe example and add this feature after verifying the linked API reference.
What To Look For¶
The point visual reads its position attribute from a scene buffer that is also bound as read-write storage for a compute shader. Static color and diameter_px arrays define the appearance, while per-frame parameters update time and the shader writes new x/y coordinates. Compare the moving markers with their stable colors and sizes; this demonstrates a useful compute-to-render pattern for particle systems, simulations, and iterative GPU analyses.
Source¶
/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/* compute_buffer_animation - This example shows a compute pass updating point positions.
*
* Scenario: features_compute_buffer_animation
* Style: features, graphite_cyan, 1280x720 window target, experimental scene compute
*
* Build: just example-c features/compute_buffer_animation
* Run: ./build/examples/c/features/compute_buffer_animation --live
* Smoke: ./build/examples/c/features/compute_buffer_animation --png
*
* What to look for: the point visual reads its position attribute from a scene buffer that is also
* bound as read-write storage for a compute shader. Static color and diameter_px arrays define the
* appearance, while per-frame parameters update time and the shader writes new x/y coordinates.
* Compare the moving markers with their stable colors and sizes; this demonstrates a useful
* compute-to-render pattern for particle systems, simulations, and iterative GPU analyses.
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include "_alloc.h"
#include "datoviz/scene.h"
#include "example_style.h"
#include "runner/scenario_runner.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define POINT_COUNT 6u
#define WORKGROUP_SIZE 1u
static const float COMPUTE_ANGULAR_SPEED = 3.30f;
/*************************************************************************************************/
/* Structs */
/*************************************************************************************************/
typedef struct ComputeBufferAnimationState
{
DvzSceneBuffer* param;
vec4 params;
} ComputeBufferAnimationState;
/*************************************************************************************************/
/* Shaders */
/*************************************************************************************************/
static const char* COMPUTE_WGSL =
"struct Params { p: vec4f, }\n"
"@group(0) @binding(0) var<storage, read> params: Params;\n"
"@group(0) @binding(1) var<storage, read_write> positions: array<f32>;\n"
"@group(0) @binding(2) var<storage, read> phases: array<f32>;\n"
"@compute @workgroup_size(1)\n"
"fn main(@builtin(global_invocation_id) global_id: vec3u) {\n"
" let i = global_id.x;\n"
" let count = u32(params.p.z);\n"
" if (i >= count) { return; }\n"
" let phase = phases[i] + params.p.x * params.p.y * (0.65 + 0.18 * f32(i));\n"
" let denom = max(1.0, f32(count - 1u));\n"
" let center = -0.70 + 1.40 * (f32(i) / denom);\n"
" let radius = 0.07 + 0.018 * f32((i * 7u) % 4u);\n"
" positions[3u * i + 0u] = center + radius * cos(phase);\n"
" positions[3u * i + 1u] = 0.18 * sin(0.7 * f32(i)) + radius * sin(phase);\n"
" positions[3u * i + 2u] = 0.0;\n"
"}\n";
static const char* COMPUTE_GLSL =
"#version 450\n"
"layout(local_size_x = 1) in;\n"
"layout(std430, set = 0, binding = 0) readonly buffer Params { vec4 p; } params;\n"
"layout(std430, set = 0, binding = 1) buffer Positions { float x[]; } positions;\n"
"layout(std430, set = 0, binding = 2) readonly buffer Phases { float x[]; } phases;\n"
"void main() {\n"
" uint i = gl_GlobalInvocationID.x;\n"
" uint count = uint(params.p.z);\n"
" if (i >= count) return;\n"
" float phase = phases.x[i] + params.p.x * params.p.y * (0.65 + 0.18 * float(i));\n"
" float center = -0.70 + 1.40 * (float(i) / max(1.0, float(count - 1u)));\n"
" float radius = 0.07 + 0.018 * float((i * 7u) % 4u);\n"
" positions.x[3u * i + 0u] = center + radius * cos(phase);\n"
" positions.x[3u * i + 1u] = 0.18 * sin(0.7 * float(i)) + radius * sin(phase);\n"
" positions.x[3u * i + 2u] = 0.0;\n"
"}\n";
/*************************************************************************************************/
/* Forward declarations */
/*************************************************************************************************/
DvzScenarioSpec dvz_example_compute_buffer_animation_scenario(void);
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
/**
* Create a scene buffer with a copied payload.
*
* @param scene scene owning the buffer
* @param usage buffer usage flags
* @param stride item stride
* @param data copied payload
* @param byte_size payload size
* @return scene buffer, or NULL
*/
static DvzSceneBuffer* _scene_buffer(
DvzScene* scene, uint32_t usage, uint32_t stride, const void* data, uint64_t byte_size)
{
DvzSceneBufferDesc desc = dvz_scene_buffer_desc();
desc.usage = usage;
desc.stride = stride;
desc.byte_size = byte_size;
DvzSceneBuffer* buffer = dvz_scene_buffer(scene, &desc);
if (buffer == NULL)
return NULL;
if (dvz_scene_buffer_set_data(buffer, data, byte_size) != DVZ_OK)
return NULL;
return buffer;
}
/**
* Upload compute parameters for one scenario time.
*
* @param state scenario state
* @param t scenario time in seconds
* @return true on success
*/
static bool _upload_compute_params(ComputeBufferAnimationState* state, double t)
{
if (state == NULL || state->param == NULL)
return false;
state->params[0] = (float)t;
state->params[1] = COMPUTE_ANGULAR_SPEED;
state->params[2] = (float)POINT_COUNT;
state->params[3] = 0.0f;
return dvz_scene_buffer_set_data(state->param, state->params, sizeof(state->params)) == DVZ_OK;
}
/**
* Add the compute-fed point visual and attach the compute pass.
*
* @param scene scene owning objects
* @param figure figure scheduling compute
* @param panel panel receiving the visual
* @param state scenario state receiving the parameter buffer
* @return true on success
*/
static bool _add_compute_points(
DvzScene* scene, DvzFigure* figure, DvzPanel* panel, ComputeBufferAnimationState* state)
{
if (state == NULL)
return false;
const vec3 positions[POINT_COUNT] = {
{-0.72f, -0.24f, 0.0f}, {-0.44f, +0.18f, 0.0f}, {-0.16f, -0.10f, 0.0f},
{+0.16f, +0.10f, 0.0f}, {+0.44f, -0.18f, 0.0f}, {+0.72f, +0.24f, 0.0f},
};
const float phases[POINT_COUNT] = {0.0f, 0.7f, 1.6f, 2.4f, 3.2f, 4.1f};
DvzColor colors[POINT_COUNT] = {
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_SECONDARY),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_WARNING),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_TEXT),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_WARNING),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY),
};
const float diameters[POINT_COUNT] = {34.0f, 42.0f, 50.0f, 50.0f, 42.0f, 34.0f};
DvzSceneBuffer* position = _scene_buffer(
scene, DVZ_SCENE_BUFFER_USAGE_VERTEX | DVZ_SCENE_BUFFER_USAGE_STORAGE, sizeof(vec3),
positions, sizeof(positions));
state->params[0] = 0.0f;
state->params[1] = COMPUTE_ANGULAR_SPEED;
state->params[2] = (float)POINT_COUNT;
state->params[3] = 0.0f;
DvzSceneBuffer* param = _scene_buffer(
scene, DVZ_SCENE_BUFFER_USAGE_STORAGE, sizeof(vec4), state->params, sizeof(state->params));
DvzSceneBuffer* phase = _scene_buffer(
scene, DVZ_SCENE_BUFFER_USAGE_STORAGE, sizeof(float), phases, sizeof(phases));
if (position == NULL || param == NULL || phase == NULL)
return false;
state->param = param;
DvzVisual* point = dvz_point(scene, 0);
if (point == NULL)
return false;
if (dvz_visual_set_attr_buffer(point, "position", position, 0, POINT_COUNT) != DVZ_OK)
return false;
if (dvz_visual_set_data(point, "color", colors, POINT_COUNT) != 0)
return false;
if (dvz_visual_set_data(point, "diameter_px", diameters, POINT_COUNT) != 0)
return false;
DvzPointStyleDesc style = dvz_point_style_desc();
style.aspect = DVZ_SHAPE_ASPECT_FILLED;
style.stroke_width_px = 0.0f;
if (dvz_point_set_style(point, &style) != 0)
return false;
if (dvz_visual_set_depth_test(point, false) != 0)
return false;
if (dvz_panel_add_visual(panel, point, NULL) != 0)
return false;
DvzSceneComputeDesc compute_desc = dvz_scene_compute_desc();
compute_desc.label = "feature_compute_buffer_animation";
#ifdef DVZ_EXAMPLE_NO_APP
compute_desc.shader_format = DVZ_SCENE_SHADER_FORMAT_WGSL;
compute_desc.shader_source = COMPUTE_WGSL;
#else
compute_desc.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL;
compute_desc.shader_source = COMPUTE_GLSL;
#endif
compute_desc.dispatch[0] = (POINT_COUNT + WORKGROUP_SIZE - 1u) / WORKGROUP_SIZE;
compute_desc.dispatch[1] = 1u;
compute_desc.dispatch[2] = 1u;
DvzSceneCompute* compute = dvz_scene_compute(scene, &compute_desc);
if (compute == NULL)
return false;
if (dvz_scene_compute_set_buffer(
compute, 0, param, DVZ_SCENE_COMPUTE_ACCESS_READ, 0, sizeof(state->params)) != DVZ_OK)
return false;
if (dvz_scene_compute_set_buffer(
compute, 1, position, DVZ_SCENE_COMPUTE_ACCESS_READ_WRITE, 0, sizeof(positions)) !=
DVZ_OK)
return false;
if (dvz_scene_compute_set_buffer(
compute, 2, phase, DVZ_SCENE_COMPUTE_ACCESS_READ, 0, sizeof(phases)) != DVZ_OK)
return false;
return dvz_figure_add_compute(figure, compute) == DVZ_OK;
}
/*************************************************************************************************/
/* Scenario callbacks */
/*************************************************************************************************/
/**
* Initialize the scene-compute-buffer feature scenario.
*
* @param ctx scenario context
* @param out_user scenario state output
* @return true on success
*/
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
if (ctx == NULL || out_user == NULL)
return false;
ComputeBufferAnimationState* state =
(ComputeBufferAnimationState*)dvz_calloc(1, sizeof(ComputeBufferAnimationState));
if (state == NULL)
return false;
ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
if (ctx->figure == NULL)
goto error;
DvzPanel* panel = dvz_panel_full(ctx->figure);
if (panel == NULL)
goto error;
example_graphite_cyan_set_panel_background(panel);
if (!_add_compute_points(ctx->scene, ctx->figure, panel, state))
goto error;
*out_user = state;
return true;
error:
dvz_free(state);
return false;
}
/**
* Advance the scene-compute-buffer scenario for one frame.
*
* @param ctx scenario context
* @param user scenario state
*/
static void _scenario_frame(DvzScenarioContext* ctx, void* user)
{
if (ctx == NULL || user == NULL)
return;
const double time = ctx->preview_mode ? dvz_scenario_preview_time(ctx) : ctx->time;
(void)_upload_compute_params((ComputeBufferAnimationState*)user, time);
}
/**
* Destroy the scene-compute-buffer scenario.
*
* @param ctx scenario context
* @param user scenario state
*/
static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
(void)ctx;
dvz_free(user);
}
/**
* Return the scene-compute-buffer scenario specification.
*
* @return scenario specification
*/
DvzScenarioSpec dvz_example_compute_buffer_animation_scenario(void)
{
return (DvzScenarioSpec){
.id = "features_compute_buffer_animation",
.title = "Compute Buffer Animation",
.width = WIDTH,
.height = HEIGHT,
.fps = 60.0,
.requirements = DVZ_SCENARIO_REQ_POINT_VISUAL | DVZ_SCENARIO_REQ_SCENE_BUFFERS |
DVZ_SCENARIO_REQ_STORAGE_BUFFERS | DVZ_SCENARIO_REQ_SCENE_COMPUTE |
DVZ_SCENARIO_REQ_FRAME_CALLBACKS | DVZ_SCENARIO_REQ_CONTINUOUS_FRAMES,
.continuous_frames = true,
.init = _scenario_init,
.frame = _scenario_frame,
.destroy = _scenario_destroy,
};
}
/*************************************************************************************************/
/* Functions */
/*************************************************************************************************/
/**
* Run the scene-compute-buffer feature example through the native scenario runner.
*
* @param argc command-line argument count
* @param argv command-line argument vector
* @return process exit code
*/
#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
DvzScenarioSpec spec = dvz_example_compute_buffer_animation_scenario();
return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details
- ID:
features_compute_buffer_animation - Category:
feature - Lane:
features - Status:
experimental - Source:
examples/c/features/compute_buffer_animation.c - Approved adaptation starter:
no - Browser support: Live in browser
- WebGPU live route:
examples/webgpu/live.html?id=features_compute_buffer_animation - Browser capability tags:
scene-buffers,storage-buffers,scene-compute,continuous-frames,point - Validation:
smoke+screenshot
Tags
scene-compute, storage-buffer, point
Data
| Field | Value |
|---|---|
kind |
synthetic |
