diff --git a/src/films/display.cpp b/src/films/display.cpp index d08ff5b2..caa0f68b 100644 --- a/src/films/display.cpp +++ b/src/films/display.cpp @@ -2,10 +2,12 @@ // Created by Mike Smith on 2023/6/14. // +#include + #include #include -#include +#include #include #include @@ -96,15 +98,20 @@ class DisplayInstance final : public Film::Instance { private: luisa::unique_ptr _base; - luisa::unique_ptr _window; + luisa::unique_ptr _window; Image _framebuffer; - Swapchain _swapchain; - Shader2D<> _blit; + Shader2D _blit; + Shader2D> _clear; Clock _clock; - mutable double _last_frame_time; + Stream *_stream{}; + ImTextureID _background{}; + mutable double _last_frame_time{}; + mutable int _tone_mapping{}; + mutable float _exposure{}; + mutable int _background_fit{}; private: - [[nodiscard]] auto _tone_mapping_uncharted2(Expr color) noexcept { + [[nodiscard]] static auto _tone_mapping_uncharted2(Expr color) noexcept { static constexpr auto a = 0.15f; static constexpr auto b = 0.50f; static constexpr auto c = 0.10f; @@ -117,7 +124,7 @@ class DisplayInstance final : public Film::Instance { }; return op(1.6f * color) / op(white); } - [[nodiscard]] auto _tone_mapping_aces(Expr color) noexcept { + [[nodiscard]] static auto _tone_mapping_aces(Expr color) noexcept { constexpr auto a = 2.51f; constexpr auto b = 0.03f; constexpr auto c = 2.43f; @@ -125,17 +132,19 @@ class DisplayInstance final : public Film::Instance { constexpr auto e = 0.14f; return (color * (a * color + b)) / (color * (c * color + d) + e); } - [[nodiscard]] auto _linear_to_srgb(Expr color) noexcept { + [[nodiscard]] static auto _linear_to_srgb(Expr color) noexcept { return ite(color <= .0031308f, color * 12.92f, 1.055f * pow(color, 1.f / 2.4f) - .055f); } public: - DisplayInstance(const Pipeline &pipeline, const Film *film, + DisplayInstance(const Pipeline &pipeline, const Display *film, luisa::unique_ptr base) noexcept : Film::Instance{pipeline, film}, - _base{std::move(base)} {} + _base{std::move(base)}, + _tone_mapping{luisa::to_underlying(film->tone_mapping())}, + _exposure{film->exposure().x} {} [[nodiscard]] Film::Accumulation read(Expr pixel) const noexcept override { return _base->read(pixel); @@ -146,32 +155,37 @@ class DisplayInstance final : public Film::Instance { auto &&device = pipeline().device(); auto size = node()->resolution(); if (!_window) { - _window = luisa::make_unique("Display", size); auto d = node(); - auto option = SwapchainOption{ - .display = _window->native_display(), - .window = _window->native_handle(), - .size = size, - .wants_hdr = d->hdr(), - .wants_vsync = d->vsync(), - .back_buffer_count = d->back_buffers()}; - _swapchain = device.create_swapchain(*command_buffer.stream(), option); - _framebuffer = device.create_image( - _swapchain.backend_storage(), size); - _blit = device.compile<2>([&] { + _stream = command_buffer.stream(); + auto window_size = size; + while (std::max(window_size.x, window_size.y) > 2048u) { + window_size /= 2u; + } + _window = luisa::make_unique( + device, *command_buffer.stream(), "Display", + ImGuiWindow::Config{ + .size = window_size, + .vsync = d->vsync(), + .hdr = d->hdr(), + .back_buffers = d->back_buffers()}); + _framebuffer = device.create_image(_window->swapchain().backend_storage(), size); + _background = reinterpret_cast(_window->register_texture(_framebuffer, TextureSampler::linear_point_zero())); + _blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float scale) noexcept { auto p = dispatch_id().xy(); - auto exposure = d->exposure(); - exposure = make_float3(std::exp2(exposure.x), std::exp2(exposure.y), std::exp2(exposure.z)); - auto color = _base->read(p).average * exposure; - switch (d->tone_mapping()) { - case Display::ToneMapping::NONE: break; - case Display::ToneMapping::UNCHARTED2: color = _tone_mapping_uncharted2(color); break; - case Display::ToneMapping::ACES: color = _tone_mapping_aces(color); break; - } - if (_framebuffer.storage() == PixelStorage::BYTE4) {// LDR + auto color = base->read(p).average * scale; + $switch (tonemapping) { + $case (static_cast(Display::ToneMapping::NONE)) {}; + $case (static_cast(Display::ToneMapping::UNCHARTED2)) { color = _tone_mapping_uncharted2(color); }; + $case (static_cast(Display::ToneMapping::ACES)) { color = _tone_mapping_aces(color); }; + $default { unreachable(); }; + }; + $if (ldr) {// LDR color = _linear_to_srgb(color); - } - _framebuffer->write(p, make_float4(color, 1.f)); + }; + framebuffer->write(p, make_float4(color, 1.f)); + }); + _clear = device.compile<2>([](ImageFloat image) noexcept { + image.write(dispatch_id().xy(), make_float4(0.f)); }); } _last_frame_time = _clock.toc(); @@ -186,27 +200,75 @@ class DisplayInstance final : public Film::Instance { } void release() noexcept override { - while (_window && !_window->should_close()) { - _window->poll_events(); - } + _window = nullptr; _framebuffer = {}; - _swapchain = {}; - _window.reset(); _base->release(); } +private: + [[nodiscard]] float2 _compute_background_size(const ImGuiViewport *viewport, int fit) const noexcept { + auto frame_size = make_float2(_framebuffer.size()); + auto viewport_size = make_float2(viewport->Size.x, viewport->Size.y); + switch (fit) { + case 0: {// aspect fill + auto aspect = frame_size.x / frame_size.y; + auto viewport_aspect = viewport_size.x / viewport_size.y; + auto ratio = aspect < viewport_aspect ? viewport_size.x / frame_size.x : viewport_size.y / frame_size.y; + return frame_size * ratio; + } + case 1: {// aspect fit + auto aspect = frame_size.x / frame_size.y; + auto viewport_aspect = viewport_size.x / viewport_size.y; + auto ratio = aspect > viewport_aspect ? viewport_size.x / frame_size.x : viewport_size.y / frame_size.y; + return frame_size * ratio; + } + case 2: {// stretch + return viewport_size; + } + default: break; + } + return viewport_size; + } + + void _display() const noexcept { + auto scale = luisa::exp2(_exposure); + auto is_ldr = _window->framebuffer().storage() != PixelStorage::FLOAT4; + auto size = _framebuffer.size(); + *_stream << _blit(_tone_mapping, is_ldr, scale).dispatch(size); + auto viewport = ImGui::GetMainViewport(); + auto bg_size = _compute_background_size(viewport, _background_fit); + auto p_min = make_float2(viewport->Pos.x, viewport->Pos.y) + + .5f * (make_float2(viewport->Size.x, viewport->Size.y) - bg_size); + ImGui::GetBackgroundDrawList()->AddImage(_background, + ImVec2{p_min.x, p_min.y}, + ImVec2{p_min.x + bg_size.x, p_min.y + bg_size.y}); + ImGui::Begin("Console", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::Text("Display FPS: %.2f", ImGui::GetIO().Framerate); + ImGui::SliderFloat("Exposure", &_exposure, -10.f, 10.f); + constexpr const char *const tone_mapping_names[] = {"None", "Uncharted2", "ACES"}; + ImGui::Combo("Tone Mapping", &_tone_mapping, tone_mapping_names, std::size(tone_mapping_names)); + constexpr const char *const fit_names[] = {"Fill", "Fit", "Stretch"}; + ImGui::Combo("Background Fit", &_background_fit, fit_names, std::size(fit_names)); + } + ImGui::End(); + } + bool show(CommandBuffer &command_buffer) const noexcept override { + LUISA_ASSERT(command_buffer.stream() == _stream, "Command buffer stream mismatch."); auto interval = 1. / node()->target_fps(); if (auto current_time = _clock.toc(); current_time - _last_frame_time >= interval) { _last_frame_time = current_time; - _window->poll_events(); if (_window->should_close()) { command_buffer << synchronize(); exit(0);// FIXME: exit gracefully } - command_buffer << _blit().dispatch(node()->resolution()) - << _swapchain.present(_framebuffer); + command_buffer << commit(); + _window->prepare_frame(); + *_stream << _clear(_window->framebuffer()).dispatch(_window->framebuffer().size()); + _display(); + _window->render_frame(); return true; } return false;