Files
Renderive/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp
T
2026-08-16 18:33:24 +08:00

1198 lines
52 KiB
C++

#include <renderive/error/Error_Policy.hpp>
#include "Datoviz_Visual_Backend.h"
#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 <stdexcept>
#include <string>
#include <utility>
namespace renderive::render_3d::detail {
namespace {
constexpr std::uint64_t color_target_id = 0x5256504f494e54ULL;
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)
::renderive::error::unexpected<std::runtime_error>(message);
return resource;
}
int modifiers(::renderive::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>(::renderive::Keyboard_Modifier::Shift)) != 0)
result |= DVZ_KEY_MODIFIER_SHIFT;
if ((bits & static_cast<std::uint8_t>(::renderive::Keyboard_Modifier::Ctrl)) != 0)
result |= DVZ_KEY_MODIFIER_CONTROL;
if ((bits & static_cast<std::uint8_t>(::renderive::Keyboard_Modifier::Alt)) != 0)
result |= DVZ_KEY_MODIFIER_ALT;
if ((bits & static_cast<std::uint8_t>(::renderive::Keyboard_Modifier::Meta)) != 0)
result |= DVZ_KEY_MODIFIER_SUPER;
return result;
}
DvzPointerButton button(::renderive::Mouse_Button value) {
switch (value) {
case ::renderive::Mouse_Button::Left:
return DVZ_POINTER_BUTTON_LEFT;
case ::renderive::Mouse_Button::Middle:
return DVZ_POINTER_BUTTON_MIDDLE;
case ::renderive::Mouse_Button::Right:
return DVZ_POINTER_BUTTON_RIGHT;
case ::renderive::Mouse_Button::None:
return DVZ_POINTER_BUTTON_NONE;
}
return DVZ_POINTER_BUTTON_NONE;
}
DvzKeyCode key_code(::renderive::Key key, std::uint32_t native_key) {
switch (key) {
case ::renderive::Key::Escape:
return DVZ_KEY_ESCAPE;
case ::renderive::Key::Enter:
return DVZ_KEY_ENTER;
case ::renderive::Key::Space:
return DVZ_KEY_SPACE;
case ::renderive::Key::Delete:
return DVZ_KEY_DELETE;
case ::renderive::Key::Backspace:
return DVZ_KEY_BACKSPACE;
case ::renderive::Key::Left:
return DVZ_KEY_LEFT;
case ::renderive::Key::Right:
return DVZ_KEY_RIGHT;
case ::renderive::Key::Up:
return DVZ_KEY_UP;
case ::renderive::Key::Down:
return DVZ_KEY_DOWN;
case ::renderive::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;
}
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))
::renderive::error::unexpected<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)
::renderive::error::unexpected<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())
::renderive::error::unexpected<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)
::renderive::error::unexpected<std::runtime_error>("failed to upload Datoviz text geometry");
}
} // namespace
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())
::renderive::error::unexpected<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())
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz readback buffer");
} catch (...) {
destroy();
::renderive::error::unexpected(
"creating Datoviz frame target", std::current_exception());
}
}
~Frame_Target() noexcept(false) { destroy(); }
void begin(bool observe) {
if (in_flight_)
::renderive::error::unexpected<std::logic_error>("Datoviz frame target is still in flight");
observing_ = observe;
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)
::renderive::error::unexpected<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 {
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);
}
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_)
::renderive::error::unexpected<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);
}
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);
if (observing_ && timestamps_supported_) {
vkCmdWriteTimestamp(command_buffer,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
query_pool_, 3);
}
if (dvz_cmd_end_result(commands_) != 0)
::renderive::error::unexpected<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)
::renderive::error::unexpected<std::runtime_error>("failed to submit Datoviz point frame");
in_flight_ = true;
completed_layout_ = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
}
[[nodiscard]] Collection collect() {
if (!in_flight_)
::renderive::error::unexpected<std::logic_error>("Datoviz frame target has no pending frame");
Collection result;
try {
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;
::renderive::error::unexpected(
"submitting Datoviz frame target", std::current_exception());
}
in_flight_ = false;
observing_ = false;
return result;
}
void discard_after_completion() {
if (!in_flight_)
::renderive::error::unexpected<std::logic_error>("Datoviz frame target has no pending frame");
in_flight_ = false;
observing_ = 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;
}
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);
};
return Datoviz_Gpu_Timing{
elapsed(timestamps[0], timestamps[1]),
elapsed(timestamps[1], timestamps[2]),
elapsed(timestamps[2], timestamps[3]),
elapsed(timestamps[0], timestamps[3])};
}
void destroy() {
if (in_flight_)
::renderive::error::unexpected<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 timestamps_initialized_{};
bool timestamps_supported_{};
};
Datoviz_Visual_Backend::Datoviz_Visual_Backend(
std::uint32_t gpu_index, bool validation_enabled,
const Scene_State& initial_scene)
: domain_thread_(std::this_thread::get_id()) {
try {
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)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz 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)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz DRP2 runtime");
create_scene(initial_scene);
} catch (...) {
destroy();
::renderive::error::unexpected(
"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_)
::renderive::error::unexpected<std::logic_error>("Datoviz objects may only be used on the render domain");
}
void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) {
const Visual_Family family = initial_scene.visual_family;
scene_ = dvz_scene();
if (scene_ == nullptr)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz scene");
const auto capabilities = dvz_capability_snapshot();
if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<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)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz visual family");
DvzCameraDesc camera = dvz_camera_desc();
camera.view.eye[0] = 0.0F;
camera.view.eye[1] = 0.0F;
camera.view.eye[2] = 4.0F;
camera.view.target[0] = 0.0F;
camera.view.target[1] = 0.0F;
camera.view.target[2] = 0.0F;
camera.projection.near_clip = 0.01F;
camera.projection.far_clip = 100.0F;
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to create Datoviz point camera");
DvzController* controller = dvz_arcball(scene_, nullptr);
if (controller == nullptr ||
dvz_panel_bind_controller(panel_, controller, DVZ_DIM_MASK_XYZ) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to bind Datoviz arcball controller");
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)
::renderive::error::unexpected<std::runtime_error>("failed to connect Datoviz point input");
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(
const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point) {
require_domain();
if (scene_revision != applied_scene_revision_) {
if (dvz_figure_resize(figure_, scene.viewport.width,
scene.viewport.height) != DVZ_OK)
::renderive::error::unexpected<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);
applied_scene_revision_ = scene_revision;
}
if (point.state_revision != applied_state_revision_) {
const auto& state = point.state;
mat4 transform{};
for (std::size_t row = 0; row < 4; ++row)
for (std::size_t column = 0; column < 4; ++column)
transform[row][column] = state.transform.values[row * 4 + column];
if (point.family == Visual_Family::Point) {
DvzPointStyleDesc style = dvz_point_style_desc();
style.edge_color.r = state.style.edge_color.red;
style.edge_color.g = state.style.edge_color.green;
style.edge_color.b = state.style.edge_color.blue;
style.edge_color.a = state.style.edge_color.alpha;
style.stroke_width_px = state.style.stroke_width_px;
style.aspect = aspect(state.style.aspect);
if (dvz_point_set_style(visual_, &style) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to apply Datoviz point style");
}
if (dvz_visual_set_transform(visual_, transform) != DVZ_OK ||
dvz_visual_set_depth_test(visual_, state.depth_test) != DVZ_OK ||
dvz_visual_set_visible(
visual_, state.visible && !point.positions.empty()) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to apply Datoviz visual state");
applied_state_revision_ = point.state_revision;
}
if (point.data_revision == applied_data_revision_)
return;
if (point.positions.empty()) {
if (dvz_visual_set_visible(visual_, false) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to hide empty Datoviz point visual");
applied_data_revision_ = point.data_revision;
return;
}
const auto count = static_cast<std::uint32_t>(point.positions.size());
DvzResult result = DVZ_OK;
switch (point.family) {
case Visual_Family::Point: {
const std::array<DvzVisualDataUpdate, 3> updates{{
{"position", point.positions.data(), count},
{"color", point.colors.data(), count},
{"diameter_px", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"sigma", point.sigma.data(), count},
{"angle", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"pixel_size_px", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"diameter_px", point.sizes.data(), count},
{"angle", point.angles.data(), count},
{"shape", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"radius", point.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", point.positions.data(), count},
{"position_end", point.secondary_positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.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", point.positions.data(), count},
{"vector", point.secondary_positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"normal", point.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", point.positions.data(), count},
{"color", point.colors.data(), count},
{"stroke_width_px", point.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", point.positions.data(), count},
{"extent", point.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.state.visible) != DVZ_OK)
::renderive::error::unexpected<std::runtime_error>("failed to upload Datoviz visual payload");
applied_data_revision_ = point.data_revision;
}
void Datoviz_Visual_Backend::dispatch_pointer(
::renderive::Event_Type event, float x, float y,
::renderive::Mouse_Button mouse_button,
::renderive::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
const float width = static_cast<float>(viewport.width);
const float height = static_cast<float>(viewport.height);
const DvzPointerEventType type =
event == ::renderive::Event_Type::Pointer_Move
? DVZ_POINTER_EVENT_MOVE
: event == ::renderive::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,
::renderive::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
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 ::renderive::Key_Event& event) {
require_domain();
const DvzKeyboardEventType type =
event.type == ::renderive::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_State& 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 = dvz_capability_snapshot();
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);
}
::renderive::error::unexpected<std::runtime_error>(message);
}
return artifact;
}
std::optional<Datoviz_Visual_Backend::Pending_Frame>
Datoviz_Visual_Backend::submit(
const Scene_State& scene, std::uint64_t scene_revision,
const Prepared_Point& point, std::uint64_t frame_sequence,
bool observe) {
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, scene_revision, point);
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>(gpu_context_, target_extent_,
++target_generation_);
}
target_->begin(observe);
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();
::renderive::error::unexpected(
"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();
::renderive::error::unexpected<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));
}
::renderive::error::unexpected<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)
::renderive::error::unexpected<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);
auto output = std::make_shared<Pixel_Frame>();
output->extent = pending.extent;
output->sequence = pending.sequence;
output->rgba8 = std::move(collection.pixels);
return {std::move(output), std::move(pending.trace)};
}
void Datoviz_Visual_Backend::discard(Pending_Frame pending) {
require_domain();
if (target_ == nullptr ||
target_->generation() != pending.target_generation)
::renderive::error::unexpected<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_)
::renderive::error::unexpected<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 (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;
panel_ = nullptr;
figure_ = nullptr;
if (scene_ != nullptr) {
dvz_scene_destroy(scene_);
scene_ = nullptr;
}
if (gpu_context_ != nullptr) {
dvz_gpu_ctx_destroy(gpu_context_);
gpu_context_ = nullptr;
}
}
} // namespace renderive::render_3d::detail