397 lines
15 KiB
C++
397 lines
15 KiB
C++
#include "Point_Scene.h"
|
|
|
|
#include "detail/Datoviz_Visual_Backend.h"
|
|
#include "detail/Point_Core.h"
|
|
#include "detail/Render_Domain.h"
|
|
|
|
#include <renderive/frame_control/Frame_Control.hpp>
|
|
#include <renderive/scene/Scene.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
namespace renderive::render_3d {
|
|
namespace {
|
|
|
|
void validate(Extent extent) {
|
|
if (extent.empty())
|
|
throw std::invalid_argument("Point_Scene viewport must be nonempty");
|
|
}
|
|
|
|
void validate(Clear_Color color) {
|
|
const auto component = [](float value) {
|
|
return std::isfinite(value) && value >= 0.0F && value <= 1.0F;
|
|
};
|
|
if (!component(color.red) || !component(color.green) ||
|
|
!component(color.blue) || !component(color.alpha))
|
|
throw std::invalid_argument("Point_Scene clear color must be in [0, 1]");
|
|
}
|
|
|
|
float datoviz_wheel_step(float pixel_delta, float angle_delta) noexcept {
|
|
return angle_delta != 0.0F ? angle_delta / 120.0F
|
|
: pixel_delta / 100.0F;
|
|
}
|
|
|
|
struct Scene_Model {
|
|
virtual ~Scene_Model() = default;
|
|
virtual void resize(Extent extent) = 0;
|
|
virtual void set_clear_color(Clear_Color color) = 0;
|
|
virtual void dispatch_pointer(
|
|
::renderive::Event_Type type, float x, float y,
|
|
::renderive::Mouse_Button button,
|
|
::renderive::Keyboard_Modifier modifiers) = 0;
|
|
virtual void dispatch_wheel(
|
|
float x, float y, float delta_x, float delta_y,
|
|
::renderive::Keyboard_Modifier modifiers) = 0;
|
|
virtual void dispatch_key(const ::renderive::Key_Event& event) = 0;
|
|
[[nodiscard]] virtual bool prepare_frame() = 0;
|
|
[[nodiscard]] virtual bool refresh_manual_frame() = 0;
|
|
[[nodiscard]] virtual bool discard_pending_frame() = 0;
|
|
[[nodiscard]] virtual bool render_prepared_frame() = 0;
|
|
[[nodiscard]] virtual bool request_frame() = 0;
|
|
[[nodiscard]] virtual std::shared_ptr<const Pixel_Frame> latest_frame()
|
|
const = 0;
|
|
[[nodiscard]] virtual Frame_Status frame_status() const = 0;
|
|
[[nodiscard]] virtual Scene_Base& scene() noexcept = 0;
|
|
[[nodiscard]] virtual const Scene_Base& scene() const noexcept = 0;
|
|
};
|
|
|
|
using Manual_Frame = ::Manual_Refresh_Strategy<::Scene3D_Frame_Data>;
|
|
using Low_Latency_Frame = ::Low_Latency_Strategy<::Scene3D_Frame_Data>;
|
|
using Playback_Frame = ::Flow_Refresh_Strategy<::Scene3D_Frame_Data>;
|
|
|
|
template <class Strategy, Frame_Mode Mode>
|
|
struct Basic_Point_Scene final
|
|
: ::Scene3D_Context<Strategy, detail::Scene_State>,
|
|
::Scene_Renderer,
|
|
Scene_Model {
|
|
using Kernel_Scene = ::Scene3D_Context<Strategy, detail::Scene_State>;
|
|
using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy;
|
|
|
|
Basic_Point_Scene(const Scene_Options& options,
|
|
std::shared_ptr<Point_Visual> visual)
|
|
requires (!std::same_as<Strategy, Low_Latency_Frame>)
|
|
: Kernel_Scene() {
|
|
initialize(options, std::move(visual));
|
|
}
|
|
|
|
Basic_Point_Scene(const Scene_Options& options,
|
|
std::shared_ptr<Point_Visual> visual)
|
|
requires std::same_as<Strategy, Low_Latency_Frame>
|
|
: Kernel_Scene(Observer_State<>{}, typename Strategy::Configuration{
|
|
.frequency_hz = options.maximum_frames_per_second,
|
|
.replace_pending_frame = true}) {
|
|
initialize(options, std::move(visual));
|
|
}
|
|
|
|
~Basic_Point_Scene() override {
|
|
this->shutdown();
|
|
render_domain.invoke([this] { backend.reset(); });
|
|
}
|
|
|
|
void initialize(const Scene_Options& options,
|
|
std::shared_ptr<Point_Visual> point_visual) {
|
|
if (!point_visual)
|
|
throw std::invalid_argument("Point_Scene requires a Point_Visual");
|
|
visual = std::move(point_visual);
|
|
point_id = visual->renderable_id();
|
|
this->Scene_State_Strategy::template set<
|
|
&detail::Scene_State::viewport>(options.viewport);
|
|
this->Scene_State_Strategy::template set<
|
|
&detail::Scene_State::clear_color>(options.clear_color);
|
|
this->Scene_State_Strategy::template set<
|
|
&detail::Scene_State::visual_family>(options.visual_family);
|
|
{
|
|
auto builder = this->attach_builder();
|
|
builder.attach(renderive_Owner<Point_Visual>(visual));
|
|
}
|
|
const detail::Scene_State initial{
|
|
options.viewport, options.clear_color, options.visual_family};
|
|
render_domain.invoke([this, &options, &initial] {
|
|
backend = std::make_unique<detail::Datoviz_Visual_Backend>(
|
|
options.gpu_index, options.validation_enabled, initial);
|
|
});
|
|
}
|
|
|
|
void resize(Extent extent) override {
|
|
this->Scene_State_Strategy::template set<
|
|
&detail::Scene_State::viewport>(extent);
|
|
this->invalidate_renderables();
|
|
}
|
|
|
|
void set_clear_color(Clear_Color color) override {
|
|
this->Scene_State_Strategy::template set<
|
|
&detail::Scene_State::clear_color>(color);
|
|
this->notify_model_dirty();
|
|
}
|
|
|
|
void dispatch_pointer(
|
|
::renderive::Event_Type type, float x, float y,
|
|
::renderive::Mouse_Button button,
|
|
::renderive::Keyboard_Modifier modifiers) override {
|
|
const Extent viewport = this->Scene_State_Strategy::template get<
|
|
&detail::Scene_State::viewport>();
|
|
render_domain.invoke([this, type, x, y, button, modifiers, viewport] {
|
|
backend->dispatch_pointer(type, x, y, button, modifiers, viewport);
|
|
});
|
|
this->notify_model_dirty();
|
|
}
|
|
|
|
void dispatch_wheel(
|
|
float x, float y, float delta_x, float delta_y,
|
|
::renderive::Keyboard_Modifier modifiers) override {
|
|
const Extent viewport = this->Scene_State_Strategy::template get<
|
|
&detail::Scene_State::viewport>();
|
|
render_domain.invoke(
|
|
[this, x, y, delta_x, delta_y, modifiers, viewport] {
|
|
backend->dispatch_wheel(x, y, delta_x, delta_y, modifiers,
|
|
viewport);
|
|
});
|
|
this->notify_model_dirty();
|
|
}
|
|
|
|
void dispatch_key(const ::renderive::Key_Event& event) override {
|
|
render_domain.invoke([this, event] { backend->dispatch_key(event); });
|
|
this->notify_model_dirty();
|
|
}
|
|
|
|
[[nodiscard]] bool prepare_frame() override {
|
|
auto painter = this->frame_control.acquire_painter();
|
|
if (!painter)
|
|
return false;
|
|
this->publish_frame_state(*painter);
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool refresh_manual_frame() override {
|
|
if constexpr (Mode == Frame_Mode::Manual)
|
|
return this->frame_control.refresh();
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] bool discard_pending_frame() override {
|
|
if constexpr (Mode == Frame_Mode::Playback)
|
|
return false;
|
|
else
|
|
return this->frame_control.discard_pending_frame();
|
|
}
|
|
|
|
[[nodiscard]] bool render_prepared_frame() override {
|
|
auto renderer = this->frame_control.acquire_renderer();
|
|
if (!renderer)
|
|
return false;
|
|
this->render(*renderer);
|
|
this->wait_for_render();
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool request_frame() override {
|
|
if (!prepare_frame())
|
|
return false;
|
|
if constexpr (Mode == Frame_Mode::Manual) {
|
|
if (!refresh_manual_frame())
|
|
return false;
|
|
}
|
|
return render_prepared_frame();
|
|
}
|
|
|
|
[[nodiscard]] std::shared_ptr<const Pixel_Frame> latest_frame()
|
|
const override {
|
|
std::lock_guard lock(frame_mutex);
|
|
return latest;
|
|
}
|
|
|
|
[[nodiscard]] Frame_Status frame_status() const override {
|
|
Frame_Status result;
|
|
result.mode = Mode;
|
|
if constexpr (Mode == Frame_Mode::Manual) {
|
|
const auto state = this->frame_control.state();
|
|
result.produced_frame_count = state.prepared_frame_count;
|
|
result.consumed_frame_count = state.render_count;
|
|
result.dropped_frame_count = state.replaced_prepared_frame_count +
|
|
state.discarded_prepared_frame_count;
|
|
result.failed_operation_count = state.failed_refresh_count;
|
|
result.pending_frame_count = state.pending_frame ? 1U : 0U;
|
|
result.latest_sequence = state.render_frame_sequence;
|
|
} else if constexpr (Mode == Frame_Mode::Low_Latency) {
|
|
const auto state = this->frame_control.state();
|
|
const auto counters = this->frame_control.counter_statistics();
|
|
result.frequency_hz = state.frequency_hz;
|
|
result.produced_frame_count = counters.published_frame_count;
|
|
result.consumed_frame_count = counters.render_pass_count;
|
|
result.dropped_frame_count = counters.abandoned_frame_count +
|
|
counters.manually_discarded_frame_count;
|
|
result.failed_operation_count = counters.swap_failure_count;
|
|
result.pending_frame_count =
|
|
this->frame_control.pending_frame_count();
|
|
result.latest_sequence = state.frame_sequence;
|
|
result.next_refresh_interval_ns = state.next_refresh_interval_ns;
|
|
} else {
|
|
const auto state = this->frame_control.state();
|
|
result.produced_frame_count = state.enqueued_frame_count;
|
|
result.consumed_frame_count = state.rendered_frame_count;
|
|
result.failed_operation_count = state.empty_acquire_count;
|
|
result.pending_frame_count = state.pending_frame_count;
|
|
}
|
|
std::lock_guard lock(frame_mutex);
|
|
if (latest)
|
|
result.latest_sequence =
|
|
std::max(result.latest_sequence, latest->sequence);
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] Scene_Base& scene() noexcept override { return *this; }
|
|
[[nodiscard]] const Scene_Base& scene() const noexcept override {
|
|
return *this;
|
|
}
|
|
|
|
void render_scene(const Scene_Render_Context& context) override {
|
|
const auto prepared = context.prepared<detail::Prepared_Point>(point_id);
|
|
if (!prepared)
|
|
throw std::logic_error("Point_Visual did not publish prepared data");
|
|
const auto& scene_state =
|
|
context.frame.scene_state<detail::Scene_State>();
|
|
auto frame = render_domain.invoke([this, &scene_state, &context,
|
|
&prepared] {
|
|
return backend->render(scene_state,
|
|
context.frame.scene_state_revision(),
|
|
*prepared, context.frame.render_sequence);
|
|
});
|
|
if (context.metrics && frame)
|
|
context.metrics->set(
|
|
Node_Metric_Kind::pixel_count,
|
|
static_cast<std::uint64_t>(frame->extent.width) *
|
|
frame->extent.height);
|
|
std::lock_guard lock(frame_mutex);
|
|
latest = std::move(frame);
|
|
}
|
|
|
|
std::shared_ptr<Point_Visual> visual;
|
|
Renderable_Id point_id{};
|
|
detail::Render_Domain render_domain;
|
|
std::unique_ptr<detail::Datoviz_Visual_Backend> backend;
|
|
mutable std::mutex frame_mutex;
|
|
std::shared_ptr<const Pixel_Frame> latest;
|
|
};
|
|
|
|
std::unique_ptr<Scene_Model> make_scene_model(
|
|
const Scene_Options& options, std::shared_ptr<Point_Visual> visual) {
|
|
switch (options.frame_mode) {
|
|
case Frame_Mode::Manual:
|
|
return std::make_unique<
|
|
Basic_Point_Scene<Manual_Frame, Frame_Mode::Manual>>(
|
|
options, std::move(visual));
|
|
case Frame_Mode::Low_Latency:
|
|
return std::make_unique<
|
|
Basic_Point_Scene<Low_Latency_Frame, Frame_Mode::Low_Latency>>(
|
|
options, std::move(visual));
|
|
case Frame_Mode::Playback:
|
|
return std::make_unique<
|
|
Basic_Point_Scene<Playback_Frame, Frame_Mode::Playback>>(
|
|
options, std::move(visual));
|
|
}
|
|
throw std::invalid_argument("unknown Point_Scene frame mode");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct Point_Scene::Impl {
|
|
explicit Impl(const Scene_Options& options,
|
|
std::shared_ptr<Point_Visual> visual)
|
|
: model(make_scene_model(options, std::move(visual))) {}
|
|
|
|
std::unique_ptr<Scene_Model> model;
|
|
};
|
|
|
|
Point_Scene::Point_Scene(Scene_Options options,
|
|
std::shared_ptr<Point_Visual> visual) {
|
|
validate(options.viewport);
|
|
validate(options.clear_color);
|
|
if (!std::isfinite(options.maximum_frames_per_second) ||
|
|
options.maximum_frames_per_second <= 0.0)
|
|
throw std::invalid_argument(
|
|
"Point_Scene frame frequency must be positive");
|
|
impl_ = std::make_unique<Impl>(options, std::move(visual));
|
|
}
|
|
|
|
Point_Scene::~Point_Scene() = default;
|
|
|
|
void Point_Scene::resize(Extent extent) {
|
|
validate(extent);
|
|
impl_->model->resize(extent);
|
|
}
|
|
|
|
void Point_Scene::set_clear_color(Clear_Color color) {
|
|
validate(color);
|
|
impl_->model->set_clear_color(color);
|
|
}
|
|
|
|
void Point_Scene::dispatch(const ::renderive::Event&) {
|
|
// Show, hide and leave carry no Datoviz controller payload.
|
|
}
|
|
|
|
void Point_Scene::dispatch_pointer(
|
|
::renderive::Event_Type type, float x, float y,
|
|
::renderive::Mouse_Button button, ::renderive::Mouse_Button_Mask,
|
|
::renderive::Keyboard_Modifier modifiers) {
|
|
if (!std::isfinite(x) || !std::isfinite(y))
|
|
return;
|
|
if (type != ::renderive::Event_Type::Pointer_Move &&
|
|
type != ::renderive::Event_Type::Pointer_Press &&
|
|
type != ::renderive::Event_Type::Pointer_Release)
|
|
throw std::invalid_argument("Point_Scene expected a pointer event");
|
|
impl_->model->dispatch_pointer(type, x, y, button, modifiers);
|
|
}
|
|
|
|
void Point_Scene::dispatch_wheel(
|
|
float x, float y, float pixel_delta_x, float pixel_delta_y,
|
|
float angle_delta_x, float angle_delta_y,
|
|
::renderive::Keyboard_Modifier modifiers) {
|
|
if (!std::isfinite(x) || !std::isfinite(y) ||
|
|
!std::isfinite(pixel_delta_x) || !std::isfinite(pixel_delta_y) ||
|
|
!std::isfinite(angle_delta_x) || !std::isfinite(angle_delta_y))
|
|
return;
|
|
impl_->model->dispatch_wheel(
|
|
x, y, datoviz_wheel_step(pixel_delta_x, angle_delta_x),
|
|
datoviz_wheel_step(pixel_delta_y, angle_delta_y), modifiers);
|
|
}
|
|
|
|
void Point_Scene::dispatch(const ::renderive::Key_Event& event) {
|
|
impl_->model->dispatch_key(event);
|
|
}
|
|
|
|
bool Point_Scene::prepare_frame() { return impl_->model->prepare_frame(); }
|
|
bool Point_Scene::refresh_manual_frame() {
|
|
return impl_->model->refresh_manual_frame();
|
|
}
|
|
bool Point_Scene::discard_pending_frame() {
|
|
return impl_->model->discard_pending_frame();
|
|
}
|
|
bool Point_Scene::render_prepared_frame() {
|
|
return impl_->model->render_prepared_frame();
|
|
}
|
|
bool Point_Scene::request_frame() { return impl_->model->request_frame(); }
|
|
|
|
std::shared_ptr<const Pixel_Frame> Point_Scene::latest_frame() const {
|
|
return impl_->model->latest_frame();
|
|
}
|
|
|
|
Frame_Status Point_Scene::frame_status() const {
|
|
return impl_->model->frame_status();
|
|
}
|
|
|
|
Scene_Base& Point_Scene::render_scene() noexcept {
|
|
return impl_->model->scene();
|
|
}
|
|
|
|
const Scene_Base& Point_Scene::render_scene() const noexcept {
|
|
return impl_->model->scene();
|
|
}
|
|
|
|
} // namespace renderive::render_3d
|