Files
Aethera/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp
T
2026-08-22 20:08:57 +08:00

1434 lines
66 KiB
C++

#include "Datoviz_Visual_Backend.hpp"
#include "Exception.hpp"
#include <datoviz/drp2/stream.h>
#include <datoviz/scene/frame_plan.h>
#include <volk.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstring>
#include <limits>
#include <map>
#include <memory>
#include <mutex>
#include <numbers>
#include <stdexcept>
#include <string>
#include <utility>
namespace aethera::render_3d::detail {
namespace {
constexpr std::uint64_t color_target_id = 0x5256504f494e54ULL;
DvzCapabilitySnapshot offscreen_capabilities() {
auto capabilities = dvz_capability_snapshot();
capabilities.supports_color_blending = true;
return capabilities;
}
std::uint64_t trace_now_ns() noexcept {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}
template <class Resource, class Allocate>
Resource* allocate_wrapper(Allocate allocate, const char* message) {
Resource* resource = allocate();
if (resource == nullptr) throw std::runtime_error(message);
return resource;
}
int modifiers(::aethera::Keyboard_Modifier value) {
const auto bits = static_cast<std::uint8_t>(value);
int result = DVZ_KEY_MODIFIER_NONE;
if ((bits & static_cast<std::uint8_t>(::aethera::Keyboard_Modifier::shift)) != 0) result |= DVZ_KEY_MODIFIER_SHIFT;
if ((bits & static_cast<std::uint8_t>(::aethera::Keyboard_Modifier::control)) != 0) result |= DVZ_KEY_MODIFIER_CONTROL;
if ((bits & static_cast<std::uint8_t>(::aethera::Keyboard_Modifier::alt)) != 0) result |= DVZ_KEY_MODIFIER_ALT;
if ((bits & static_cast<std::uint8_t>(::aethera::Keyboard_Modifier::meta)) != 0) result |= DVZ_KEY_MODIFIER_SUPER;
return result;
}
DvzPointerButton button(::aethera::Mouse_Button value) {
switch (value) {
case ::aethera::Mouse_Button::left: return DVZ_POINTER_BUTTON_LEFT;
case ::aethera::Mouse_Button::middle: return DVZ_POINTER_BUTTON_MIDDLE;
case ::aethera::Mouse_Button::right: return DVZ_POINTER_BUTTON_RIGHT;
case ::aethera::Mouse_Button::none: return DVZ_POINTER_BUTTON_NONE;
}
return DVZ_POINTER_BUTTON_NONE;
}
DvzKeyCode key_code(::aethera::Key key, std::uint32_t native_key) {
switch (key) {
case ::aethera::Key::escape: return DVZ_KEY_ESCAPE;
case ::aethera::Key::enter: return DVZ_KEY_ENTER;
case ::aethera::Key::space: return DVZ_KEY_SPACE;
case ::aethera::Key::delete_key: return DVZ_KEY_DELETE;
case ::aethera::Key::backspace: return DVZ_KEY_BACKSPACE;
case ::aethera::Key::left: return DVZ_KEY_LEFT;
case ::aethera::Key::right: return DVZ_KEY_RIGHT;
case ::aethera::Key::up: return DVZ_KEY_UP;
case ::aethera::Key::down: return DVZ_KEY_DOWN;
case ::aethera::Key::home: return DVZ_KEY_HOME;
case ::aethera::Key::unknown: break;
}
return native_key <= static_cast<std::uint32_t>(DVZ_KEY_LAST)
? static_cast<DvzKeyCode>(native_key)
: DVZ_KEY_UNKNOWN;
}
DvzShapeAspect aspect(Point_Aspect value) {
switch (value) {
case Point_Aspect::filled: return DVZ_SHAPE_ASPECT_FILLED;
case Point_Aspect::stroke: return DVZ_SHAPE_ASPECT_STROKE;
case Point_Aspect::outline: return DVZ_SHAPE_ASPECT_OUTLINE;
}
return DVZ_SHAPE_ASPECT_FILLED;
}
float axis_position(const plot::Axis_Descriptor& axis, double coordinate) {
const auto origin = axis.range.origin;
const auto target = axis.range.target;
if (!std::isfinite(origin) || !std::isfinite(target) || origin == target)
throw std::invalid_argument("3D axis range must be finite and non-empty");
double ratio{};
if (axis.scale == plot::Axis_Scale::logarithmic) {
if (!(origin > 0.0) || !(target > 0.0) || !(coordinate > 0.0))
throw std::invalid_argument("logarithmic 3D axis coordinates must be positive");
ratio = (std::log10(coordinate) - std::log10(origin)) /
(std::log10(target) - std::log10(origin));
} else {
ratio = (coordinate - origin) / (target - origin);
}
return static_cast<float>(-1.0 + 2.0 * ratio);
}
std::string axis_title(const plot::Axis_Descriptor& axis) {
if (axis.label.empty()) return axis.unit;
if (axis.unit.empty()) return axis.label;
return axis.label + " (" + axis.unit + ")";
}
void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) {
auto font_descriptor = dvz_font_desc();
font_descriptor.family = "Roboto";
font_descriptor.style = "Regular";
auto* font = dvz_font(scene, &font_descriptor);
auto atlas_specification =
dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F);
if (font == nullptr ||
!dvz_font_atlas_ensure_string(font, &atlas_specification, text))
throw std::runtime_error("failed to create Datoviz text atlas");
const auto* atlas = dvz_font_atlas(font, &atlas_specification);
if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) throw std::runtime_error("failed to bind Datoviz text atlas");
float line_width = 0.0F;
for (const auto* character = text; *character != '\0'; ++character) {
const auto* glyph =
dvz_text_atlas_glyph(atlas, static_cast<std::uint8_t>(*character));
if (glyph != nullptr) line_width += glyph->advance;
}
std::vector<std::array<float, 3>> positions;
std::vector<std::array<float, 4>> bounds;
std::vector<std::array<float, 4>> texture_coordinates;
std::vector<std::array<std::uint8_t, 4>> colors;
std::vector<float> angles;
const auto atlas_info = dvz_text_atlas_info(atlas);
float cursor_x = 0.0F;
std::size_t glyph_index = 0;
for (const auto* character = text; *character != '\0'; ++character) {
const auto* glyph =
dvz_text_atlas_glyph(atlas, static_cast<std::uint8_t>(*character));
if (glyph == nullptr) continue;
const float x0 = cursor_x + glyph->xoff - 0.5F * line_width;
const float y0 = 0.5F * atlas_info.ascent + glyph->yoff;
const std::array<float, 4> glyph_bounds{
x0, y0, x0 + glyph->width, y0 + glyph->height
};
const std::array<float, 4> glyph_texture{
glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3]
};
const std::array<std::uint8_t, 4> glyph_color =
glyph_index % 2 == 0
? std::array<std::uint8_t, 4>{40, 235, 205, 255}
: std::array<std::uint8_t, 4>{255, 190, 80, 255};
for (std::uint32_t vertex = 0; vertex < 6; ++vertex) {
positions.push_back({0.0F, 0.0F, 0.0F});
bounds.push_back(glyph_bounds);
texture_coordinates.push_back(glyph_texture);
colors.push_back(glyph_color);
angles.push_back(0.0F);
}
cursor_x += glyph->advance;
++glyph_index;
}
if (positions.empty()) throw std::runtime_error("Datoviz text atlas contains no visible glyphs");
const auto count = static_cast<std::uint32_t>(positions.size());
const std::array<DvzVisualDataUpdate, 5> updates{
{
{"position", positions.data(), count}, {"bounds", bounds.data(), count},
{"texcoords", texture_coordinates.data(), count},
{"color", colors.data(), count}, {"angle", angles.data(), count}
}
};
if (dvz_visual_set_data_many(visual, updates.data(),
static_cast<std::uint32_t>(updates.size())) != DVZ_OK ||
dvz_visual_set_depth_test(visual, false) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz text geometry");
}
bool supports_item_interaction(Visual_Family family) noexcept {
return family == Visual_Family::point || family == Visual_Family::pixel ||
family == Visual_Family::marker;
}
} // namespace
class Datoviz_Render_Context final {
public:
[[nodiscard]] static std::shared_ptr<Datoviz_Render_Context> acquire(
std::uint32_t gpu_index, bool validation_enabled) {
using Key = std::pair<std::uint32_t, bool>;
static std::mutex registry_mutex;
static std::map<Key, std::weak_ptr<Datoviz_Render_Context>> registry;
std::lock_guard lock(registry_mutex);
const Key key{gpu_index, validation_enabled};
if (const auto found = registry.find(key); found != registry.end()) {
if (auto context = found->second.lock()) return context;
registry.erase(found);
}
auto context = std::shared_ptr<Datoviz_Render_Context>(
new Datoviz_Render_Context(gpu_index, validation_enabled));
registry.emplace(key, context);
return context;
}
~Datoviz_Render_Context() {
if (gpu_context_ != nullptr) dvz_gpu_ctx_destroy(gpu_context_);
}
[[nodiscard]] DvzGpuCtx* gpu_context() const noexcept { return gpu_context_; }
private:
Datoviz_Render_Context(std::uint32_t gpu_index, bool validation_enabled) {
DvzGpuCtxConfig configuration = dvz_gpu_ctx_config();
dvz_gpu_ctx_config_validation(&configuration, validation_enabled);
dvz_gpu_ctx_config_gpu(&configuration, gpu_index);
dvz_gpu_ctx_config_enable_canvas_extensions(&configuration, false);
gpu_context_ = dvz_gpu_ctx(&configuration);
if (gpu_context_ == nullptr)
throw std::runtime_error("failed to create shared Datoviz GPU context");
}
DvzGpuCtx* gpu_context_{}; /* 共享 GPU Device 与分配器的唯一所有权。 */
};
class Datoviz_Visual_Backend::Frame_Target final {
public:
struct Collection {
std::vector<std::byte> pixels;
std::optional<Datoviz_Gpu_Timing> gpu_timing;
};
Frame_Target(DvzGpuCtx* gpu_context, Extent extent, std::uint64_t generation) : gpu_context_(gpu_context), extent_(extent), generation_(generation) {
if (gpu_context == nullptr || extent.empty()) throw std::invalid_argument("invalid Datoviz point frame target");
const std::uint64_t byte_size = static_cast<std::uint64_t>(extent.width) *
extent.height * 4ULL;
if (byte_size > std::numeric_limits<DvzSize>::max()) throw std::length_error("Datoviz point frame target is too large");
byte_size_ = static_cast<DvzSize>(byte_size);
DvzDevice* device = dvz_gpu_ctx_device(gpu_context_);
DvzVma* allocator = dvz_gpu_ctx_alloc(gpu_context_);
DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN);
if (device == nullptr || allocator == nullptr || queue == nullptr) throw std::runtime_error("Datoviz GPU context is incomplete");
try {
image_ = allocate_wrapper<DvzImages>(dvz_images_create_wrapper,
"failed to allocate Datoviz image");
dvz_images(device, allocator, VK_IMAGE_TYPE_2D, 1, image_);
dvz_images_format(image_, VK_FORMAT_R8G8B8A8_UNORM);
dvz_images_size(image_, extent.width, extent.height, 1);
dvz_images_tiling(image_, VK_IMAGE_TILING_OPTIMAL);
dvz_images_usage(image_, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
dvz_images_alloc_flags(image_, DVZ_ALLOC_FLAGS_NONE);
if (dvz_images_create(image_) != 0) throw std::runtime_error("failed to create Datoviz point image");
view_ = allocate_wrapper<DvzImageViews>(dvz_image_views_create_wrapper,
"failed to allocate Datoviz image view");
dvz_image_views(image_, view_);
dvz_image_views_type(view_, VK_IMAGE_VIEW_TYPE_2D);
dvz_image_views_aspect(view_, VK_IMAGE_ASPECT_COLOR_BIT);
dvz_image_views_mip(view_, 0, 1);
dvz_image_views_layers(view_, 0, 1);
if (dvz_image_views_create(view_) != 0) throw std::runtime_error("failed to create Datoviz point image view");
commands_ = allocate_wrapper<DvzCommands>(dvz_commands_create_wrapper,
"failed to allocate Datoviz commands");
dvz_commands(device, queue, 1, commands_);
if (dvz_commands_handle(commands_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz command buffer");
fence_ = allocate_wrapper<DvzFence>(dvz_fence_create_wrapper,
"failed to allocate Datoviz fence");
dvz_fence(device, true, fence_);
if (dvz_fence_handle(fence_) == VK_NULL_HANDLE) throw std::runtime_error("failed to create Datoviz fence");
submit_ = allocate_wrapper<DvzSubmit>(dvz_submit_create_wrapper,
"failed to allocate Datoviz submit");
readback_ = allocate_wrapper<DvzBuffer>(dvz_buffer_create_wrapper,
"failed to allocate Datoviz readback");
dvz_buffer(device, allocator, readback_);
dvz_buffer_size(readback_, byte_size_);
dvz_buffer_flags(readback_, DVZ_ALLOC_HOST_ACCESS_RANDOM | DVZ_ALLOC_MAPPED);
dvz_buffer_usage(readback_, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
if (dvz_buffer_create(readback_) != 0) throw std::runtime_error("failed to create Datoviz readback buffer");
}
catch (...) {
destroy();
raise_context("creating Datoviz frame target", std::current_exception());
}
}
~Frame_Target() noexcept(false) {
destroy();
}
void begin(bool observe, bool readback) {
if (in_flight_) throw std::logic_error("Datoviz frame target is still in flight");
observing_ = observe;
readback_requested_ = readback;
if (observing_ && !timestamps_initialized_) {
initialize_timestamps(
dvz_gpu_ctx_device(gpu_context_),
dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN));
}
dvz_cmd_reset(commands_);
if (dvz_cmd_begin_result(commands_) != 0) throw std::runtime_error("failed to begin Datoviz command buffer");
DvzBarriers barriers{};
dvz_barriers(&barriers);
auto* image_barrier = dvz_barriers_image(&barriers, dvz_image_handle(image_, 0));
if (completed_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) {
dvz_barrier_image_stage(image_barrier, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
dvz_barrier_image_access(
image_barrier, 0,
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
}
else if (completed_layout_ == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
dvz_barrier_image_stage(image_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
dvz_barrier_image_access(
image_barrier, VK_ACCESS_2_TRANSFER_READ_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
}
else {
dvz_barrier_image_stage(image_barrier, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
dvz_barrier_image_access(
image_barrier, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
}
dvz_barrier_image_layout(image_barrier, completed_layout_,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT);
dvz_barrier_image_mip(image_barrier, 0, 1);
dvz_barrier_image_layers(image_barrier, 0, 1);
dvz_cmd_barriers(commands_, &barriers);
if (observing_ && timestamps_supported_) {
const VkCommandBuffer command_buffer =
dvz_commands_handle(commands_);
vkCmdResetQueryPool(command_buffer, query_pool_, 0, 4);
vkCmdWriteTimestamp(
command_buffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
query_pool_, 0);
}
recording_ = true;
}
[[nodiscard]] DvzStreamFrame stream_frame() const {
DvzStreamFrame frame{};
frame.image = dvz_image_handle(image_, 0);
frame.command_buffer = dvz_commands_handle(commands_);
frame.image_view = dvz_image_views_handle(view_, 0);
frame.extent = {extent_.width, extent_.height};
frame.color_format = VK_FORMAT_R8G8B8A8_UNORM;
frame.image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
frame.usage = DVZ_STREAM_FRAME_USAGE_RENDER_TARGET | DVZ_STREAM_FRAME_USAGE_COPY_SRC;
frame.command_buffer_recording = recording_;
frame.image_borrowed = true;
frame.image_view_borrowed = true;
frame.command_buffer_borrowed = true;
frame.handles_dirty = true;
frame.resource_generation = generation_;
frame.image_valid = true;
frame.memory_fd = -1;
frame.wait_semaphore_fd = -1;
return frame;
}
void submit() {
if (!recording_) throw std::logic_error("Datoviz frame target is not recording");
const VkCommandBuffer command_buffer = dvz_commands_handle(commands_);
if (observing_ && timestamps_supported_) {
vkCmdWriteTimestamp(
command_buffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
query_pool_, 1);
}
if (readback_requested_) {
DvzBarriers image_barriers{};
dvz_barriers(&image_barriers);
auto* image_barrier =
dvz_barriers_image(&image_barriers, dvz_image_handle(image_, 0));
dvz_barrier_image_stage(image_barrier,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_2_TRANSFER_BIT);
dvz_barrier_image_access(image_barrier,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_TRANSFER_READ_BIT);
dvz_barrier_image_layout(image_barrier,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
dvz_barrier_image_aspect(image_barrier, VK_IMAGE_ASPECT_COLOR_BIT);
dvz_barrier_image_mip(image_barrier, 0, 1);
dvz_barrier_image_layers(image_barrier, 0, 1);
dvz_cmd_barriers(commands_, &image_barriers);
if (observing_ && timestamps_supported_) {
vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
query_pool_, 2);
}
DvzImageRegion region{};
dvz_image_region(&region);
dvz_image_region_extent(&region, extent_.width, extent_.height, 1);
dvz_cmd_copy_image_to_buffer(
commands_, dvz_image_handle(image_, 0),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &region,
dvz_buffer_handle(readback_), 0);
DvzBarriers buffer_barriers{};
dvz_barriers(&buffer_barriers);
auto* buffer_barrier = dvz_barriers_buffer(
&buffer_barriers, dvz_buffer_handle(readback_), 0, byte_size_);
dvz_barrier_buffer_stage(buffer_barrier, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
VK_PIPELINE_STAGE_2_HOST_BIT);
dvz_barrier_buffer_access(buffer_barrier, VK_ACCESS_2_TRANSFER_WRITE_BIT,
VK_ACCESS_2_HOST_READ_BIT);
dvz_cmd_barriers(commands_, &buffer_barriers);
}
else if (observing_ && timestamps_supported_) {
vkCmdWriteTimestamp(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
query_pool_, 2);
}
if (observing_ && timestamps_supported_) {
vkCmdWriteTimestamp(command_buffer,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
query_pool_, 3);
}
if (dvz_cmd_end_result(commands_) != 0) throw std::runtime_error("failed to end Datoviz command buffer");
recording_ = false;
dvz_fence_reset(fence_);
dvz_submit(submit_);
dvz_submit_command(submit_, dvz_commands_handle(commands_));
DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN);
if (dvz_submit_send(submit_, dvz_queue_handle(queue),
dvz_fence_handle(fence_)) != VK_SUCCESS)
throw std::runtime_error("failed to submit Datoviz point frame");
in_flight_ = true;
completed_layout_ = readback_requested_ ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
[[nodiscard]] Collection collect() {
if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame");
Collection result;
try {
if (readback_requested_) {
result.pixels.resize(static_cast<std::size_t>(byte_size_));
dvz_buffer_download(readback_, 0, byte_size_, result.pixels.data());
}
result.gpu_timing = collect_gpu_timing();
}
catch (...) {
in_flight_ = false;
observing_ = false;
readback_requested_ = false;
raise_context("submitting Datoviz frame target", std::current_exception());
}
in_flight_ = false;
observing_ = false;
readback_requested_ = false;
return result;
}
void discard_after_completion() {
if (!in_flight_) throw std::logic_error("Datoviz frame target has no pending frame");
in_flight_ = false;
observing_ = false;
readback_requested_ = false;
}
[[nodiscard]] VkDevice device() const {
return dvz_device_handle(dvz_gpu_ctx_device(gpu_context_));
}
[[nodiscard]] VkFence fence() const {
return dvz_fence_handle(fence_);
}
[[nodiscard]] std::uint64_t generation() const noexcept {
return generation_;
}
void abort() noexcept {
if (recording_ && commands_ != nullptr) dvz_cmd_reset(commands_);
recording_ = false;
observing_ = false;
readback_requested_ = false;
}
private:
void initialize_timestamps(DvzDevice* device, DvzQueue* queue) noexcept {
timestamps_initialized_ = true;
if (device == nullptr || queue == nullptr ||
vkGetPhysicalDeviceQueueFamilyProperties == nullptr ||
vkGetPhysicalDeviceProperties == nullptr ||
vkCreateQueryPool == nullptr ||
vkCmdResetQueryPool == nullptr ||
vkCmdWriteTimestamp == nullptr ||
vkGetQueryPoolResults == nullptr)
return;
const VkPhysicalDevice physical =
dvz_device_physical_device(device);
const VkDevice logical = dvz_device_handle(device);
if (physical == VK_NULL_HANDLE || logical == VK_NULL_HANDLE) return;
std::uint32_t family_count{};
vkGetPhysicalDeviceQueueFamilyProperties(
physical, &family_count, nullptr);
if (family_count == 0) return;
std::vector<VkQueueFamilyProperties> families(family_count);
vkGetPhysicalDeviceQueueFamilyProperties(
physical, &family_count, families.data());
const std::uint32_t family = dvz_queue_family(queue);
if (family >= family_count ||
families[family].timestampValidBits == 0)
return;
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(physical, &properties);
VkQueryPoolCreateInfo configuration{
VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO
};
configuration.queryType = VK_QUERY_TYPE_TIMESTAMP;
configuration.queryCount = 4;
if (vkCreateQueryPool(logical, &configuration, nullptr,
&query_pool_) != VK_SUCCESS) {
query_pool_ = VK_NULL_HANDLE;
return;
}
timestamp_period_ns_ = properties.limits.timestampPeriod;
timestamp_valid_bits_ = families[family].timestampValidBits;
timestamps_supported_ = timestamp_period_ns_ > 0.0F;
}
[[nodiscard]] std::optional<Datoviz_Gpu_Timing> collect_gpu_timing() const noexcept {
if (!observing_ || !timestamps_supported_ ||
query_pool_ == VK_NULL_HANDLE)
return std::nullopt;
const VkDevice device =
dvz_device_handle(dvz_gpu_ctx_device(gpu_context_));
std::array<std::uint64_t, 4> timestamps{};
if (vkGetQueryPoolResults(
device, query_pool_, 0,
static_cast<std::uint32_t>(timestamps.size()),
sizeof(timestamps), timestamps.data(), sizeof(std::uint64_t),
VK_QUERY_RESULT_64_BIT) != VK_SUCCESS)
return std::nullopt;
const auto elapsed = [this](std::uint64_t begin,
std::uint64_t end) noexcept {
std::uint64_t ticks = end - begin;
if (timestamp_valid_bits_ < 64) {
const std::uint64_t mask =
(std::uint64_t{1} << timestamp_valid_bits_) - 1;
ticks &= mask;
}
const long double nanoseconds =
static_cast<long double>(ticks) * timestamp_period_ns_;
return nanoseconds >=
static_cast<long double>(
std::numeric_limits<std::uint64_t>::max())
? std::numeric_limits<std::uint64_t>::max()
: static_cast<std::uint64_t>(nanoseconds);
};
auto timing = Datoviz_Gpu_Timing{
elapsed(timestamps[0], timestamps[1]),
elapsed(timestamps[1], timestamps[2]),
elapsed(timestamps[2], timestamps[3]),
elapsed(timestamps[0], timestamps[3])
};
if (!readback_requested_) timing.copy_ns = 0;
return timing;
}
void destroy() {
if (in_flight_)
throw std::logic_error(
"Datoviz frame target is still in flight during destruction");
if (query_pool_ != VK_NULL_HANDLE && gpu_context_ != nullptr) {
vkDestroyQueryPool(
dvz_device_handle(dvz_gpu_ctx_device(gpu_context_)),
query_pool_, nullptr);
query_pool_ = VK_NULL_HANDLE;
}
if (readback_ != nullptr) {
dvz_buffer_destroy(readback_);
dvz_buffer_free(readback_);
readback_ = nullptr;
}
if (fence_ != nullptr) {
dvz_fence_destroy(fence_);
dvz_fence_free(fence_);
fence_ = nullptr;
}
if (submit_ != nullptr) {
dvz_submit_free(submit_);
submit_ = nullptr;
}
if (commands_ != nullptr) {
dvz_commands_destroy(commands_);
dvz_commands_free(commands_);
commands_ = nullptr;
}
if (view_ != nullptr) {
dvz_image_views_destroy(view_);
dvz_image_views_free(view_);
view_ = nullptr;
}
if (image_ != nullptr) {
dvz_images_destroy(image_);
dvz_images_free(image_);
image_ = nullptr;
}
}
DvzGpuCtx* gpu_context_{};
Extent extent_{};
std::uint64_t generation_{};
DvzSize byte_size_{};
DvzImages* image_{};
DvzImageViews* view_{};
DvzCommands* commands_{};
DvzFence* fence_{};
DvzSubmit* submit_{};
DvzBuffer* readback_{};
VkQueryPool query_pool_{VK_NULL_HANDLE};
VkImageLayout completed_layout_{VK_IMAGE_LAYOUT_UNDEFINED};
float timestamp_period_ns_{};
std::uint32_t timestamp_valid_bits_{};
bool recording_{};
bool in_flight_{};
bool observing_{};
bool readback_requested_{};
bool timestamps_initialized_{};
bool timestamps_supported_{};
};
Datoviz_Visual_Backend::Datoviz_Visual_Backend(
std::uint32_t gpu_index, bool validation_enabled,
Visual_Family visual_family, const Scene_3D_Parameters& initial_scene) : domain_thread_(std::this_thread::get_id()) {
try {
render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled);
auto* gpu_context = render_context_->gpu_context();
DvzDrp2RuntimeConfig runtime_configuration = dvz_drp2_runtime_vklite_config(
dvz_gpu_ctx_device(gpu_context), dvz_gpu_ctx_alloc(gpu_context));
runtime_ = dvz_drp2_runtime_vklite(&runtime_configuration);
if (runtime_ == nullptr) throw std::runtime_error("failed to create Datoviz DRP2 runtime");
create_scene(visual_family, initial_scene);
}
catch (...) {
destroy();
raise_context("creating Datoviz backend", std::current_exception());
}
}
Datoviz_Visual_Backend::~Datoviz_Visual_Backend() noexcept(false) {
destroy();
}
void Datoviz_Visual_Backend::require_domain() const {
if (std::this_thread::get_id() != domain_thread_) throw std::logic_error("Datoviz objects may only be used on the render domain");
}
void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_Parameters& initial_scene) {
scene_ = dvz_scene();
if (scene_ == nullptr) throw std::runtime_error("failed to create Datoviz scene");
const auto capabilities = offscreen_capabilities();
if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK) throw std::runtime_error("failed to configure Datoviz scene capabilities");
figure_ = dvz_figure(scene_, initial_scene.viewport.width,
initial_scene.viewport.height, 0);
panel_ = figure_ != nullptr ? dvz_panel_full(figure_) : nullptr;
if (panel_ != nullptr) {
switch (family) {
case Visual_Family::point: visual_ = dvz_point(scene_, 0);
break;
case Visual_Family::splat: visual_ = dvz_splat(scene_, 0);
break;
case Visual_Family::pixel: visual_ = dvz_pixel(scene_, 0);
break;
case Visual_Family::marker: visual_ = dvz_marker(scene_, 0);
break;
case Visual_Family::sphere: visual_ = dvz_sphere(scene_, 0);
break;
case Visual_Family::segment: visual_ = dvz_segment(scene_, 0);
break;
case Visual_Family::vector: visual_ = dvz_vector(scene_, 0);
break;
case Visual_Family::primitive: visual_ = dvz_primitive(
scene_, DVZ_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0);
break;
case Visual_Family::mesh: visual_ = dvz_mesh(scene_, 0);
break;
case Visual_Family::path: visual_ = dvz_path(scene_, 0);
break;
case Visual_Family::image: visual_ = dvz_image(scene_, 0);
break;
case Visual_Family::labels: visual_ = dvz_labels(scene_, 0);
break;
case Visual_Family::glyph:
case Visual_Family::text: visual_ = dvz_glyph(scene_, 0);
break;
case Visual_Family::volume: visual_ = dvz_volume(scene_, 0);
break;
}
}
if (visual_ != nullptr &&
dvz_visual_set_alpha_mode(visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK)
throw std::runtime_error("failed to configure Datoviz visual alpha mode");
if (visual_ != nullptr && family == Visual_Family::glyph) configure_glyph_text(scene_, visual_, "GLYPH");
if (visual_ != nullptr && family == Visual_Family::text) configure_glyph_text(scene_, visual_, "TEXT");
if (visual_ != nullptr &&
(family == Visual_Family::image || family == Visual_Family::glyph ||
family == Visual_Family::text)) {
constexpr std::uint32_t width = 32;
constexpr std::uint32_t height = 32;
std::vector<std::array<std::uint8_t, 4>> pixels(width * height);
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const bool stroke = family == Visual_Family::text
? (y < 6 || (x >= 13 && x <= 18))
: ((x / 8 + y / 8) % 2 == 0);
pixels[y * width + x] = stroke
? std::array<std::uint8_t, 4>{40, 235, 205, 255}
: std::array<std::uint8_t, 4>{18, 42, 72, 255};
}
}
auto descriptor = dvz_sampled_field_desc();
descriptor.dim = DVZ_FIELD_DIM_2D;
descriptor.format = DVZ_FIELD_FORMAT_RGBA8_UNORM;
descriptor.semantic = DVZ_FIELD_SEMANTIC_COLOR;
descriptor.color_role = DVZ_COLOR_ROLE_SRGB_COLOR;
descriptor.width = width;
descriptor.height = height;
descriptor.depth = 1;
auto* field = dvz_sampled_field(scene_, &descriptor);
auto view = dvz_field_data_view();
view.data = pixels.data();
view.bytes_per_row = width * sizeof(pixels.front());
view.rows_per_image = height;
if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK)
throw std::runtime_error("failed to create Datoviz 2D sampled field");
}
if (visual_ != nullptr && family == Visual_Family::labels) {
constexpr std::uint32_t width = 8;
constexpr std::uint32_t height = 8;
std::array<std::int32_t, width * height> labels{};
for (std::uint32_t y = 0; y < height; ++y)
for (std::uint32_t x = 0; x < width; ++x) labels[y * width + x] = static_cast<std::int32_t>((x / 4) + 2 * (y / 4));
auto descriptor = dvz_sampled_field_desc();
descriptor.dim = DVZ_FIELD_DIM_2D;
descriptor.format = DVZ_FIELD_FORMAT_R32_SINT;
descriptor.semantic = DVZ_FIELD_SEMANTIC_LABEL;
descriptor.color_role = DVZ_COLOR_ROLE_DATA;
descriptor.width = width;
descriptor.height = height;
descriptor.depth = 1;
auto* field = dvz_sampled_field(scene_, &descriptor);
auto view = dvz_field_data_view();
view.data = labels.data();
view.bytes_per_row = width * sizeof(labels.front());
view.rows_per_image = height;
auto scale_descriptor = dvz_scale_desc();
scale_descriptor.kind = DVZ_SCALE_CATEGORICAL;
auto* scale = dvz_scale(scene_, &scale_descriptor);
const std::array<DvzScaleCategory, 4> categories{
{
{.category_id = 0, .order = 0, .label = "north west", .color = {235, 70, 70, 255}},
{.category_id = 1, .order = 1, .label = "north east", .color = {70, 220, 100, 255}},
{.category_id = 2, .order = 2, .label = "south west", .color = {70, 120, 245, 255}},
{.category_id = 3, .order = 3, .label = "south east", .color = {245, 210, 55, 255}},
}
};
if (field == nullptr || scale == nullptr ||
dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK ||
dvz_scale_set_categories(scale, categories.data(),
static_cast<std::uint32_t>(categories.size())) != DVZ_OK ||
dvz_visual_set_scale(visual_, "labels", scale) != DVZ_OK)
throw std::runtime_error("failed to create Datoviz label field");
}
if (visual_ != nullptr && family == Visual_Family::volume) {
constexpr std::uint32_t side = 16;
std::vector<std::uint8_t> voxels(side * side * side);
for (std::uint32_t z = 0; z < side; ++z) {
for (std::uint32_t y = 0; y < side; ++y) {
for (std::uint32_t x = 0; x < side; ++x) {
const float dx = static_cast<float>(x) - 7.5F;
const float dy = static_cast<float>(y) - 7.5F;
const float dz = static_cast<float>(z) - 7.5F;
const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
voxels[(z * side + y) * side + x] =
distance < 6.5F ? static_cast<std::uint8_t>(255 - distance * 22) : 0;
}
}
}
auto descriptor = dvz_sampled_field_desc();
descriptor.dim = DVZ_FIELD_DIM_3D;
descriptor.format = DVZ_FIELD_FORMAT_R8_UNORM;
descriptor.semantic = DVZ_FIELD_SEMANTIC_SCALAR;
descriptor.color_role = DVZ_COLOR_ROLE_DATA;
descriptor.width = side;
descriptor.height = side;
descriptor.depth = side;
auto* field = dvz_sampled_field(scene_, &descriptor);
auto view = dvz_field_data_view();
view.data = voxels.data();
view.bytes_per_row = side;
view.rows_per_image = side;
if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK ||
dvz_visual_set_field(visual_, "field", field) != DVZ_OK ||
dvz_volume_set_render_mode(visual_, DVZ_VOLUME_RENDER_MIP) != DVZ_OK ||
dvz_volume_set_step_count(visual_, 48) != DVZ_OK)
throw std::runtime_error("failed to create Datoviz volume field");
}
if (figure_ == nullptr || panel_ == nullptr || visual_ == nullptr ||
dvz_panel_add_visual(panel_, visual_, nullptr) != DVZ_OK)
throw std::runtime_error("failed to create Datoviz visual family");
if (supports_item_interaction(family)) {
if (dvz_visual_set_query_capabilities(
visual_, DVZ_QUERY_CAPABILITY_ITEM) != DVZ_OK)
throw std::runtime_error("failed to enable Datoviz item queries");
item_interaction_ = dvz_item_interaction(panel_, nullptr);
if (item_interaction_ == nullptr)
throw std::runtime_error("failed to create Datoviz item interaction");
}
axes_visual_ = dvz_segment(scene_, 0);
axes_text_ = dvz_text(panel_, 0);
if (axes_visual_ == nullptr || axes_text_ == nullptr ||
dvz_segment_set_caps(axes_visual_, DVZ_SEGMENT_CAP_BUTT,
DVZ_SEGMENT_CAP_BUTT) != DVZ_OK ||
dvz_visual_set_alpha_mode(axes_visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK ||
dvz_panel_add_visual(panel_, axes_visual_, nullptr) != DVZ_OK)
throw std::runtime_error("failed to create Datoviz 3D axes visuals");
DvzTextPlacement axes_placement = dvz_text_placement();
axes_placement.mode = DVZ_TEXT_PLACEMENT_DATA;
axes_placement.anchor = DVZ_SCENE_ANCHOR_DATA;
axes_placement.depth_test = false;
DvzTextStyle axes_style = dvz_text_style();
axes_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS;
axes_style.size_px = 12.0F;
if (dvz_text_set_placement(axes_text_, &axes_placement) != DVZ_OK ||
dvz_text_set_style(axes_text_, &axes_style) != DVZ_OK)
throw std::runtime_error("failed to configure Datoviz 3D axes text");
apply_axes(initial_scene);
input_router_ = dvz_input_router();
gesture_handler_ = input_router_ != nullptr
? dvz_pointer_gesture_handler(input_router_)
: nullptr;
if (input_router_ == nullptr || gesture_handler_ == nullptr ||
dvz_panel_connect_input(panel_, input_router_) != DVZ_OK)
throw std::runtime_error("failed to connect Datoviz point input");
apply_camera(initial_scene.camera);
DvzInputResizeEvent resize{
initial_scene.viewport.width, initial_scene.viewport.height,
initial_scene.viewport.width, initial_scene.viewport.height, 1.0F, 1.0F
};
dvz_input_emit_resize(input_router_, &resize);
}
void Datoviz_Visual_Backend::apply_axes(const Scene_3D_Parameters& scene) {
const std::array descriptors{scene.x_axis, scene.y_axis, scene.z_axis};
if (applied_axes_ && *applied_axes_ == descriptors) return;
using Position = std::array<float, 3>;
std::vector<Position> starts;
std::vector<Position> ends;
std::vector<DvzColor> colors;
std::vector<float> widths;
const DvzColor axis_color{190, 207, 226, 255};
const DvzColor grid_color{58, 70, 86, 255};
const DvzColor tick_color{145, 164, 188, 255};
const auto segment = [&](Position start, Position end, DvzColor color,
float width) {
starts.push_back(start);
ends.push_back(end);
colors.push_back(color);
widths.push_back(width);
};
std::vector<std::string> strings;
std::vector<DvzTextItem> text_items;
const auto text = [&](std::string value, Position position,
std::array<float, 2> offset,
std::array<float, 2> anchor, float size,
DvzColor color) {
strings.push_back(std::move(value));
DvzTextItem item{};
item.struct_size = sizeof(DvzTextItem);
item.position[0] = position[0];
item.position[1] = position[1];
item.position[2] = position[2];
item.offset[0] = offset[0];
item.offset[1] = offset[1];
item.anchor[0] = anchor[0];
item.anchor[1] = anchor[1];
item.size_px = size;
item.color = color;
text_items.push_back(item);
};
const auto append_axis = [&](const plot::Axis_Descriptor& axis,
std::size_t dimension) {
if (!axis.visible) return;
const auto ticks = plot::axis_ticks(axis);
if (dimension == 0)
segment({-1, -1, -1}, {1, -1, -1}, axis_color, 2.2F);
else if (dimension == 1)
segment({-1, -1, -1}, {-1, 1, -1}, axis_color, 2.2F);
else
segment({-1, -1, -1}, {-1, -1, 1}, axis_color, 2.2F);
for (const auto& tick : ticks) {
const auto position = axis_position(axis, tick.coordinate);
if (dimension == 0) {
segment({position, -1, -1}, {position, -1.055F, -1},
tick_color, 1.4F);
if (axis.grid_visible)
segment({position, -1, -1}, {position, 1, -1},
grid_color, 1.0F);
if (axis.labels_visible)
text(tick.label, {position, -1, -1}, {0, 12}, {.5F, 0},
11, tick_color);
} else if (dimension == 1) {
segment({-1, position, -1}, {-1.055F, position, -1},
tick_color, 1.4F);
if (axis.grid_visible)
segment({-1, position, -1}, {1, position, -1},
grid_color, 1.0F);
if (axis.labels_visible)
text(tick.label, {-1, position, -1}, {-10, 0}, {1, .5F},
11, tick_color);
} else {
segment({-1, -1, position}, {-1.055F, -1, position},
tick_color, 1.4F);
if (axis.grid_visible) {
segment({-1, -1, position}, {1, -1, position},
grid_color, 1.0F);
segment({-1, -1, position}, {-1, 1, position},
grid_color, 1.0F);
}
if (axis.labels_visible)
text(tick.label, {-1, -1, position}, {-10, 0}, {1, .5F},
11, tick_color);
}
}
const auto title = axis_title(axis);
if (title.empty()) return;
if (dimension == 0)
text(title, {0, -1, -1}, {0, 34}, {.5F, 0}, 14, axis_color);
else if (dimension == 1)
text(title, {-1, 0, -1}, {-78, 0}, {1, .5F}, 14, axis_color);
else
text(title, {-1, -1, 0}, {-78, 0}, {1, .5F}, 14, axis_color);
};
append_axis(descriptors[0], 0);
append_axis(descriptors[1], 1);
append_axis(descriptors[2], 2);
const auto segment_count = static_cast<std::uint32_t>(starts.size());
const std::array<DvzVisualDataUpdate, 4> updates{{
{"position_start", starts.data(), segment_count},
{"position_end", ends.data(), segment_count},
{"color", colors.data(), segment_count},
{"stroke_width_px", widths.data(), segment_count}}};
if (dvz_visual_set_data_many(axes_visual_, updates.data(),
static_cast<std::uint32_t>(updates.size())) != DVZ_OK ||
dvz_visual_set_visible(axes_visual_, !starts.empty()) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz 3D axes geometry");
for (std::size_t index = 0; index < text_items.size(); ++index)
text_items[index].string = strings[index].c_str();
if (dvz_text_set_items(
axes_text_, text_items.empty() ? nullptr : text_items.data(),
static_cast<std::uint32_t>(text_items.size())) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz 3D axes labels");
applied_axes_ = descriptors;
}
void Datoviz_Visual_Backend::apply_camera(const plot::Camera_Descriptor& source) {
if (applied_camera_ && *applied_camera_ == source) return;
if (turntable_ != nullptr) {
if (input_router_ != nullptr)
(void)dvz_turntable_disconnect(turntable_, input_router_);
dvz_turntable_destroy(turntable_);
turntable_ = nullptr;
}
DvzCameraDesc camera = dvz_camera_desc();
const auto assign = [](vec3 target, plot::Spatial_Point value) {
target[0] = static_cast<float>(value.x);
target[1] = static_cast<float>(value.y);
target[2] = static_cast<float>(value.z);
};
assign(camera.view.eye, source.initial_view.eye);
assign(camera.view.target, source.initial_view.target);
assign(camera.view.up, source.initial_view.up);
camera.projection.type = source.projection == plot::Camera_Projection::orthographic
? DVZ_CAMERA_ORTHOGRAPHIC
: DVZ_CAMERA_PERSPECTIVE;
camera.projection.fov_y = static_cast<float>(
source.vertical_field_of_view_degrees * std::numbers::pi / 180.0);
camera.projection.near_clip = static_cast<float>(source.near_plane);
camera.projection.far_clip = static_cast<float>(source.far_plane);
const auto dx = source.initial_view.eye.x - source.initial_view.target.x;
const auto dy = source.initial_view.eye.y - source.initial_view.target.y;
const auto dz = source.initial_view.eye.z - source.initial_view.target.z;
const auto distance = std::sqrt(dx * dx + dy * dy + dz * dz);
camera.projection.ortho_height = static_cast<float>(
2.0 * distance *
std::tan(source.vertical_field_of_view_degrees * std::numbers::pi / 360.0));
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz Camera component");
DvzTurntableDesc turntable = dvz_turntable_desc();
turntable.initial_view = camera.view;
turntable.yaw_speed = static_cast<float>(source.control.yaw_speed);
turntable.pitch_speed = static_cast<float>(source.control.pitch_speed);
turntable.zoom_speed = static_cast<float>(source.control.zoom_speed);
turntable.pan_speed = static_cast<float>(source.control.pan_speed);
turntable.min_pitch = static_cast<float>(source.control.minimum_pitch);
turntable.max_pitch = static_cast<float>(source.control.maximum_pitch);
turntable.min_distance = static_cast<float>(source.control.minimum_distance);
turntable.max_distance = static_cast<float>(source.control.maximum_distance);
turntable.controller_flags = DVZ_TURNTABLE_FLAGS_WRAP_YAW |
DVZ_TURNTABLE_FLAGS_CLAMP_DISTANCE;
if (source.control.pan_enabled)
turntable.controller_flags |= DVZ_TURNTABLE_FLAGS_ALLOW_PAN;
turntable_ = dvz_turntable_create(&turntable);
if (turntable_ == nullptr ||
dvz_turntable_set_camera(turntable_, dvz_panel_camera(panel_)) != DVZ_OK ||
dvz_turntable_connect(turntable_, input_router_) != DVZ_OK)
throw std::runtime_error("failed to connect Datoviz Turntable to Panel Camera");
applied_camera_ = source;
}
void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepared_Visual& point) {
require_domain();
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
const auto& data = *point.data;
if (target_extent_ != scene.viewport) {
if (dvz_figure_resize(figure_, scene.viewport.width,
scene.viewport.height) != DVZ_OK)
throw std::runtime_error("failed to resize Datoviz point figure");
DvzInputResizeEvent resize{
scene.viewport.width, scene.viewport.height,
scene.viewport.width, scene.viewport.height, 1.0F, 1.0F
};
dvz_input_emit_resize(input_router_, &resize);
}
apply_camera(scene.camera);
apply_axes(scene);
if (point.revision == applied_visual_revision_) return;
{
mat4 transform{};
for (std::size_t row = 0; row < 4; ++row)
for (std::size_t column = 0; column < 4; ++column) transform[row][column] = point.transform.values[row * 4 + column];
if (point.family == Visual_Family::point) {
DvzPointStyleDesc style = dvz_point_style_desc();
style.edge_color.r = point.point_style.edge_color.red;
style.edge_color.g = point.point_style.edge_color.green;
style.edge_color.b = point.point_style.edge_color.blue;
style.edge_color.a = point.point_style.edge_color.alpha;
style.stroke_width_px = point.point_style.stroke_width_px;
style.aspect = aspect(point.point_style.aspect);
if (dvz_point_set_style(visual_, &style) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz point style");
}
if (dvz_visual_set_transform(visual_, transform) != DVZ_OK ||
dvz_visual_set_depth_test(visual_, point.depth_test) != DVZ_OK ||
dvz_visual_set_visible(
visual_, point.visible && !data.positions.empty()) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz visual state");
}
if (data.positions.empty()) {
if (dvz_visual_set_visible(visual_, false) != DVZ_OK) throw std::runtime_error("failed to hide empty Datoviz point visual");
applied_visual_revision_ = point.revision;
return;
}
const auto count = static_cast<std::uint32_t>(data.positions.size());
DvzResult result = DVZ_OK;
switch (point.family) {
case Visual_Family::point: {
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"diameter_px", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::splat: {
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"sigma", data.sigma.data(), count},
{"angle", data.angles.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::pixel: {
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"pixel_size_px", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::marker: {
const std::array<DvzVisualDataUpdate, 5> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"diameter_px", data.sizes.data(), count},
{"angle", data.angles.data(), count},
{"shape", data.shapes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 5);
break;
}
case Visual_Family::sphere: {
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"radius", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::segment: {
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position_start", data.positions.data(), count},
{"position_end", data.secondary_positions.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::vector: {
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position", data.positions.data(), count},
{"vector", data.secondary_positions.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 4);
break;
}
case Visual_Family::primitive:
case Visual_Family::mesh: {
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"normal", data.normals.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::path: {
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 3);
break;
}
case Visual_Family::image:
case Visual_Family::labels: {
const std::array<DvzVisualDataUpdate, 2> updates{
{
{"position", data.positions.data(), count},
{"extent", data.extents.data(), count}
}
};
result = dvz_visual_set_data_many(visual_, updates.data(), 2);
break;
}
case Visual_Family::glyph:
case Visual_Family::text:
case Visual_Family::volume: break;
}
if (result != DVZ_OK ||
dvz_visual_set_visible(visual_, point.visible) != DVZ_OK)
throw std::runtime_error("failed to upload Datoviz visual payload");
applied_visual_revision_ = point.revision;
}
void Datoviz_Visual_Backend::dispatch_pointer(
::aethera::Event_Type event, float x, float y,
::aethera::Mouse_Button mouse_button,
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
if (applied_camera_) {
if (mouse_button == ::aethera::Mouse_Button::left &&
!applied_camera_->control.rotate_enabled) return;
if ((mouse_button == ::aethera::Mouse_Button::middle ||
mouse_button == ::aethera::Mouse_Button::right) &&
!applied_camera_->control.pan_enabled) return;
}
const float width = static_cast<float>(viewport.width);
const float height = static_cast<float>(viewport.height);
const DvzPointerEventType type =
event == ::aethera::Event_Type::pointer_move
? DVZ_POINTER_EVENT_MOVE
: event == ::aethera::Event_Type::pointer_press
? DVZ_POINTER_EVENT_PRESS
: DVZ_POINTER_EVENT_RELEASE;
dvz_pointer_emit_position(input_router_, type, x, y, width, height,
button(mouse_button),
modifiers(keyboard_modifiers), 1.0F,
dvz_input_timestamp_ns(), nullptr);
}
void Datoviz_Visual_Backend::dispatch_wheel(
float x, float y, float delta_x, float delta_y,
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
if (applied_camera_ && !applied_camera_->control.zoom_enabled) return;
dvz_pointer_emit_wheel(
input_router_, x, y, static_cast<float>(viewport.width),
static_cast<float>(viewport.height), delta_x, delta_y,
modifiers(keyboard_modifiers), 1.0F, dvz_input_timestamp_ns(), nullptr);
}
void Datoviz_Visual_Backend::dispatch_key(
const ::aethera::Key_Event& event) {
require_domain();
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
if (turntable_ == nullptr || dvz_turntable_reset(turntable_) != DVZ_OK)
throw std::runtime_error("failed to reset Datoviz Turntable Camera");
return;
}
const DvzKeyboardEventType type =
event.type == ::aethera::Event_Type::key_release
? DVZ_KEYBOARD_EVENT_RELEASE
: event.auto_repeat
? DVZ_KEYBOARD_EVENT_REPEAT
: DVZ_KEYBOARD_EVENT_PRESS;
dvz_keyboard_emit(input_router_, type,
key_code(event.key, event.native_key),
modifiers(event.modifiers), nullptr);
}
DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(
const Scene_3D_Parameters& scene) {
DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config();
configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL;
configuration.external_color_target = true;
configuration.color_target_id = color_target_id;
configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM;
configuration.target_width = scene.viewport.width;
configuration.target_height = scene.viewport.height;
configuration.clear_color[0] = scene.clear_color.red;
configuration.clear_color[1] = scene.clear_color.green;
configuration.clear_color[2] = scene.clear_color.blue;
configuration.clear_color[3] = scene.clear_color.alpha;
const auto capabilities = offscreen_capabilities();
DvzDiagnosticReport report{};
dvz_diagnostic_report_init(&report);
auto* artifact =
dvz_figure_emit_frame(figure_, &capabilities, &report, &configuration);
if (artifact == nullptr) {
std::string message = "failed to emit Datoviz visual frame";
const auto count = dvz_diagnostic_report_count(&report);
if (count != 0) {
if (const char* diagnostic = dvz_diagnostic_report_get(&report, 0)) message += ": " + std::string(diagnostic);
}
throw std::runtime_error(message);
}
return artifact;
}
std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::submit(
const Scene_3D_Parameters& scene, const Prepared_Visual& point,
std::uint64_t frame_sequence, bool observe, bool readback) {
require_domain();
if (scene.viewport.empty()) return std::nullopt;
Datoviz_Frame_Trace trace;
trace.render_sequence = frame_sequence;
trace.observed = observe;
std::uint64_t phase_started = observe ? trace_now_ns() : 0;
apply(scene, point);
if (item_interaction_ != nullptr) {
static_cast<void>(dvz_figure_process_queries(figure_, runtime_, nullptr));
DvzQueryResult query{};
bool resolved{};
while (dvz_scene_poll_query(scene_, &query)) resolved = true;
if (resolved) {
if (hover_readout_ != nullptr) {
dvz_pinned_readout_destroy(hover_readout_);
hover_readout_ = nullptr;
}
if (query.hit) {
if (query.value_kind == DVZ_QUERY_VALUE_NONE) {
if (query.has_data_position || query.has_visual_position) {
query.value_kind = DVZ_QUERY_VALUE_VEC3;
const auto& position = query.has_data_position
? query.data_position
: query.visual_position;
std::ranges::copy(position, query.vector);
constexpr char position_label[] = "Position";
std::ranges::copy(position_label, query.label);
} else {
query.value_kind = DVZ_QUERY_VALUE_SCALAR;
query.scalar = static_cast<double>(query.item_id);
constexpr char item_label[] = "Item";
std::ranges::copy(item_label, query.label);
}
}
hover_readout_ = dvz_pinned_readout_query(panel_, &query);
}
}
}
if (observe) trace.apply_ns = trace_now_ns() - phase_started;
if (target_ == nullptr || target_extent_ != scene.viewport) {
target_.reset();
target_extent_ = scene.viewport;
target_ = std::make_unique<Frame_Target>(render_context_->gpu_context(), target_extent_,
++target_generation_);
}
target_->begin(observe, readback);
DvzSceneFrameArtifact* artifact{};
try {
if (observe) phase_started = trace_now_ns();
artifact = emit(scene);
if (observe) trace.emit_ns = trace_now_ns() - phase_started;
}
catch (...) {
target_->abort();
raise_context("preparing Datoviz frame", std::current_exception());
}
if (observe) {
trace.artifact_status = static_cast<std::uint32_t>(
dvz_scene_frame_artifact_status(artifact));
trace.artifact_resource_version =
dvz_scene_frame_artifact_resource_version(artifact);
trace.artifact_frame_index =
dvz_scene_frame_artifact_frame_index(artifact);
}
const DvzDrp2CommandStream* stream = dvz_scene_frame_artifact_stream(artifact);
const DvzStreamFrame target_frame = target_->stream_frame();
if (observe) phase_started = trace_now_ns();
const bool attached = stream != nullptr &&
dvz_drp2_runtime_attach_frame_target(
runtime_, color_target_id, &target_frame);
const DvzDrp2ValidationResult result =
attached
? dvz_drp2_runtime_execute(runtime_, stream)
: DvzDrp2ValidationResult{};
if (observe) trace.execute_ns = trace_now_ns() - phase_started;
trace.validation_ok = attached && result.ok;
trace.validation_code = static_cast<std::uint32_t>(result.code);
trace.validation_command_index = result.command_index;
if (observe || !trace.validation_ok) {
if (char* json = dvz_scene_frame_artifact_json(
artifact, "renderive_frame")) {
trace.artifact_json = json;
dvz_drp2_stream_json_destroy(json);
}
}
dvz_scene_frame_artifact_destroy(artifact);
if (!attached) {
target_->abort();
throw std::runtime_error("failed to attach the Datoviz point frame target");
}
if (!result.ok) {
target_->abort();
std::string message =
"failed to execute Datoviz point frame: validation code " +
std::to_string(static_cast<unsigned>(result.code)) +
", command " + std::to_string(result.command_index);
if (!trace.artifact_json.empty()) {
message += ", artifact " +
trace.artifact_json.substr(
0, std::min<std::size_t>(trace.artifact_json.size(), 2048));
}
throw std::runtime_error(std::move(message));
}
if (observe) phase_started = trace_now_ns();
target_->submit();
if (observe) trace.submit_ns = trace_now_ns() - phase_started;
return Pending_Frame{
target_->device(), target_->fence(), scene.viewport,
frame_sequence, target_->generation(),
std::move(trace)
};
}
Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect(
Pending_Frame pending) {
require_domain();
if (target_ == nullptr ||
target_->generation() != pending.target_generation)
throw std::logic_error("Datoviz pending frame target no longer exists");
const std::uint64_t readback_started = pending.trace.observed
? trace_now_ns()
: 0;
auto collection = target_->collect();
if (pending.trace.observed) pending.trace.readback_ns = trace_now_ns() - readback_started;
pending.trace.gpu = std::move(collection.gpu_timing);
return {pending.extent, std::move(collection.pixels), std::move(pending.trace)};
}
void Datoviz_Visual_Backend::discard(Pending_Frame pending) {
require_domain();
if (target_ == nullptr ||
target_->generation() != pending.target_generation)
throw std::logic_error("Datoviz pending frame target no longer exists");
target_->discard_after_completion();
}
void Datoviz_Visual_Backend::destroy() {
if (std::this_thread::get_id() != domain_thread_)
throw std::logic_error(
"Datoviz backend may only be destroyed on the render domain");
if (runtime_ != nullptr) {
dvz_drp2_runtime_destroy(runtime_);
runtime_ = nullptr;
}
target_.reset();
if (turntable_ != nullptr) {
if (input_router_ != nullptr)
(void)dvz_turntable_disconnect(turntable_, input_router_);
dvz_turntable_destroy(turntable_);
turntable_ = nullptr;
}
if (item_interaction_ != nullptr) {
dvz_item_interaction_destroy(item_interaction_);
item_interaction_ = nullptr;
}
if (hover_readout_ != nullptr) {
dvz_pinned_readout_destroy(hover_readout_);
hover_readout_ = nullptr;
}
if (panel_ != nullptr && input_router_ != nullptr) (void)dvz_panel_connect_input(panel_, nullptr);
if (gesture_handler_ != nullptr) {
dvz_pointer_gesture_handler_destroy(gesture_handler_);
gesture_handler_ = nullptr;
}
if (input_router_ != nullptr) {
dvz_input_router_destroy(input_router_);
input_router_ = nullptr;
}
visual_ = nullptr;
axes_visual_ = nullptr;
axes_text_ = nullptr;
applied_camera_.reset();
applied_axes_.reset();
panel_ = nullptr;
figure_ = nullptr;
if (scene_ != nullptr) {
dvz_scene_destroy(scene_);
scene_ = nullptr;
}
render_context_.reset();
}
} // namespace aethera::render_3d::detail