1198 lines
52 KiB
C++
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(®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);
|
|
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
|