1434 lines
66 KiB
C++
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(®ion);
|
|
dvz_image_region_extent(®ion, extent_.width, extent_.height, 1);
|
|
dvz_cmd_copy_image_to_buffer(
|
|
commands_, dvz_image_handle(image_, 0),
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, ®ion,
|
|
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
|
|
|
|
|