diff --git a/bin/Icons/ui/L1.png b/bin/Icons/ui/L1.png new file mode 100644 index 0000000000..8a1d87c540 Binary files /dev/null and b/bin/Icons/ui/L1.png differ diff --git a/bin/Icons/ui/L2.png b/bin/Icons/ui/L2.png new file mode 100644 index 0000000000..3b938d19ac Binary files /dev/null and b/bin/Icons/ui/L2.png differ diff --git a/bin/Icons/ui/R1.png b/bin/Icons/ui/R1.png new file mode 100644 index 0000000000..722e405c60 Binary files /dev/null and b/bin/Icons/ui/R1.png differ diff --git a/bin/Icons/ui/R2.png b/bin/Icons/ui/R2.png new file mode 100644 index 0000000000..f05529fea9 Binary files /dev/null and b/bin/Icons/ui/R2.png differ diff --git a/bin/Icons/ui/circle.png b/bin/Icons/ui/circle.png new file mode 100644 index 0000000000..6ed88bb0a6 Binary files /dev/null and b/bin/Icons/ui/circle.png differ diff --git a/bin/Icons/ui/cross.png b/bin/Icons/ui/cross.png new file mode 100644 index 0000000000..f74e38379c Binary files /dev/null and b/bin/Icons/ui/cross.png differ diff --git a/bin/Icons/ui/dpad.png b/bin/Icons/ui/dpad.png new file mode 100644 index 0000000000..5a31103da5 Binary files /dev/null and b/bin/Icons/ui/dpad.png differ diff --git a/bin/Icons/ui/dpad_down.png b/bin/Icons/ui/dpad_down.png new file mode 100644 index 0000000000..dddcc0d479 Binary files /dev/null and b/bin/Icons/ui/dpad_down.png differ diff --git a/bin/Icons/ui/dpad_left.png b/bin/Icons/ui/dpad_left.png new file mode 100644 index 0000000000..78beb6350c Binary files /dev/null and b/bin/Icons/ui/dpad_left.png differ diff --git a/bin/Icons/ui/dpad_right.png b/bin/Icons/ui/dpad_right.png new file mode 100644 index 0000000000..94a6590b33 Binary files /dev/null and b/bin/Icons/ui/dpad_right.png differ diff --git a/bin/Icons/ui/dpad_up.png b/bin/Icons/ui/dpad_up.png new file mode 100644 index 0000000000..5bc87051e6 Binary files /dev/null and b/bin/Icons/ui/dpad_up.png differ diff --git a/bin/Icons/ui/fade_bottom.png b/bin/Icons/ui/fade_bottom.png new file mode 100644 index 0000000000..dbb7d04b4c Binary files /dev/null and b/bin/Icons/ui/fade_bottom.png differ diff --git a/bin/Icons/ui/fade_top.png b/bin/Icons/ui/fade_top.png new file mode 100644 index 0000000000..661225ec8b Binary files /dev/null and b/bin/Icons/ui/fade_top.png differ diff --git a/bin/Icons/ui/left_stick.png b/bin/Icons/ui/left_stick.png new file mode 100644 index 0000000000..101b99cb17 Binary files /dev/null and b/bin/Icons/ui/left_stick.png differ diff --git a/bin/Icons/ui/new.png b/bin/Icons/ui/new.png new file mode 100644 index 0000000000..ac21f0e9e6 Binary files /dev/null and b/bin/Icons/ui/new.png differ diff --git a/bin/Icons/ui/right_stick.png b/bin/Icons/ui/right_stick.png new file mode 100644 index 0000000000..374f589bf9 Binary files /dev/null and b/bin/Icons/ui/right_stick.png differ diff --git a/bin/Icons/ui/save.png b/bin/Icons/ui/save.png new file mode 100644 index 0000000000..c1683f7223 Binary files /dev/null and b/bin/Icons/ui/save.png differ diff --git a/bin/Icons/ui/select.png b/bin/Icons/ui/select.png new file mode 100644 index 0000000000..b493aff682 Binary files /dev/null and b/bin/Icons/ui/select.png differ diff --git a/bin/Icons/ui/square.png b/bin/Icons/ui/square.png new file mode 100644 index 0000000000..0be20e14c9 Binary files /dev/null and b/bin/Icons/ui/square.png differ diff --git a/bin/Icons/ui/start.png b/bin/Icons/ui/start.png new file mode 100644 index 0000000000..3822fd85ff Binary files /dev/null and b/bin/Icons/ui/start.png differ diff --git a/bin/Icons/ui/triangle.png b/bin/Icons/ui/triangle.png new file mode 100644 index 0000000000..50ba8572d8 Binary files /dev/null and b/bin/Icons/ui/triangle.png differ diff --git a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp index c28d259d1c..b5cb274c18 100644 --- a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp @@ -2,6 +2,7 @@ #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/RSX/GSRender.h" #include "cellSysutil.h" #include "cellMsgDialog.h" @@ -60,13 +61,6 @@ s32 cellMsgDialogOpen2(u32 type, vm::cptr msgString, vm::ptr(Emu.GetCallbacks().get_msg_dialog); - - if (!dlg) - { - return CELL_SYSUTIL_ERROR_BUSY; - } - if (_type.se_mute_on) { // TODO @@ -81,6 +75,33 @@ s32 cellMsgDialogOpen2(u32 type, vm::cptr msgString, vm::ptr()) + { + if (auto dlg = rsxthr->shell_open_message_dialog()) + { + dlg->show(msgString.get_ptr(), _type, [callback, userData](s32 status) + { + if (callback) + { + sysutil_register_cb([=](ppu_thread& ppu) -> s32 + { + callback(ppu, status, userData); + return CELL_OK; + }); + } + }); + + return CELL_OK; + } + } + + const auto dlg = fxm::import(Emu.GetCallbacks().get_msg_dialog); + + if (!dlg) + { + return CELL_SYSUTIL_ERROR_BUSY; + } + dlg->type = _type; dlg->on_close = [callback, userData, wptr = std::weak_ptr(dlg)](s32 status) @@ -213,6 +234,33 @@ s32 cellMsgDialogClose(f32 delay) { cellSysutil.warning("cellMsgDialogClose(delay=%f)", delay); + extern u64 get_system_time(); + const u64 wait_until = get_system_time() + static_cast(std::max(delay, 0.0f) * 1000); + + if (auto rsxthr = fxm::get()) + { + if (auto dlg = rsxthr->shell_get_current_dialog()) + { + thread_ctrl::spawn("cellMsgDialogClose() Thread", [=] + { + while (get_system_time() < wait_until) + { + if (Emu.IsStopped()) + return; + + if (rsxthr->shell_get_current_dialog() != dlg) + return; + + std::this_thread::sleep_for(1ms); + } + + dlg->close(); + }); + + return CELL_OK; + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -220,10 +268,6 @@ s32 cellMsgDialogClose(f32 delay) return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } - extern u64 get_system_time(); - - const u64 wait_until = get_system_time() + static_cast(std::max(delay, 0.0f) * 1000); - thread_ctrl::spawn("cellMsgDialogClose() Thread", [=]() { while (dlg->state == MsgDialogState::Open && get_system_time() < wait_until) @@ -243,6 +287,14 @@ s32 cellMsgDialogAbort() { cellSysutil.warning("cellMsgDialogAbort()"); + if (auto rsxthr = fxm::get()) + { + if (rsxthr->shell_close_dialog()) + { + return CELL_OK; + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -263,6 +315,15 @@ s32 cellMsgDialogProgressBarSetMsg(u32 progressBarIndex, vm::cptr msgStrin { cellSysutil.warning("cellMsgDialogProgressBarSetMsg(progressBarIndex=%d, msgString=%s)", progressBarIndex, msgString); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_set_message(progressBarIndex, msgString.get_ptr()); + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -287,6 +348,15 @@ s32 cellMsgDialogProgressBarReset(u32 progressBarIndex) { cellSysutil.warning("cellMsgDialogProgressBarReset(progressBarIndex=%d)", progressBarIndex); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_reset(progressBarIndex); + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -311,6 +381,15 @@ s32 cellMsgDialogProgressBarInc(u32 progressBarIndex, u32 delta) { cellSysutil.warning("cellMsgDialogProgressBarInc(progressBarIndex=%d, delta=%d)", progressBarIndex, delta); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_increment(progressBarIndex, (f32)delta); + } + } + const auto dlg = fxm::get(); if (!dlg) diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 1c3615aac9..2f45e39e1b 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -355,18 +355,7 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, while (funcList) { // Display Save Data List asynchronously in the GUI thread. - atomic_t dlg_result(false); - - Emu.CallAfter([&]() - { - selected = Emu.GetCallbacks().get_save_dialog()->ShowSaveDataList(save_entries, focused, operation, listSet); - dlg_result = true; - }); - - while (!dlg_result) - { - thread_ctrl::wait_for(1000); - } + selected = Emu.GetCallbacks().get_save_dialog()->ShowSaveDataList(save_entries, focused, operation, listSet); // UI returns -1 for new save games if (selected == -1) diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index f7f1ee6112..46c4738ef3 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -534,9 +534,8 @@ error_code sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, vm::pt sceNpTrophy.error("Failed to get info for trophy dialog. Error code %x", ret); *details = SceNpTrophyDetails(); } - Emu.CallAfter([det = *details, trophyIconData]() { - Emu.GetCallbacks().get_trophy_notification_dialog()->ShowTrophyNotification(det, trophyIconData); - }); + + Emu.GetCallbacks().get_trophy_notification_dialog()->ShowTrophyNotification(*details, trophyIconData); } return CELL_OK; diff --git a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp index b1d3d6d729..5b34c42d2e 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp +++ b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp @@ -301,7 +301,7 @@ void D3D12GSRender::on_exit() return GSRender::on_exit(); } -void D3D12GSRender::do_local_task() +void D3D12GSRender::do_local_task(bool) { //TODO m_frame->clear_wm_events(); diff --git a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h index 18a94718e3..7674cd13f5 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h +++ b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h @@ -173,7 +173,7 @@ private: protected: virtual void on_init_thread() override; virtual void on_exit() override; - virtual void do_local_task() override; + virtual void do_local_task(bool idle) override; virtual bool do_method(u32 cmd, u32 arg) override; virtual void end() override; virtual void flip(int buffer) override; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 7417ba8e81..be3fe8b014 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -32,6 +32,7 @@ GLGSRender::GLGSRender() : GSRender() m_vertex_cache.reset(new gl::weak_vertex_cache()); supports_multidraw = !g_cfg.video.strict_rendering_mode; + supports_native_ui = (bool)g_cfg.misc.use_native_interface; } extern CellGcmContextData current_context; @@ -614,9 +615,6 @@ void GLGSRender::on_init_thread() { GSRender::on_init_thread(); - m_frame->disable_wm_event_queue(); - m_frame->hide(); - gl::init(); //Enable adaptive vsync if vsync is requested @@ -756,16 +754,71 @@ void GLGSRender::on_init_thread() glEnable(GL_CLIP_DISTANCE0 + 5); m_depth_converter.create(); + m_ui_renderer.create(); m_gl_texture_cache.initialize(); m_thread_id = std::this_thread::get_id(); - m_shaders_cache->load(); + if (!supports_native_ui) + { + m_frame->disable_wm_event_queue(); + m_frame->hide(); - m_frame->enable_wm_event_queue(); - m_frame->show(); + m_shaders_cache->load(nullptr); + + m_frame->enable_wm_event_queue(); + m_frame->show(); + } + else + { + struct native_helper : gl::shader_cache::progress_dialog_helper + { + rsx::thread *owner = nullptr; + rsx::overlays::message_dialog *dlg = nullptr; + + native_helper(GLGSRender *ptr) : + owner(ptr) {} + + void create() override + { + MsgDialogType type = {}; + type.disable_cancel = true; + type.progress_bar_count = 1; + + dlg = owner->shell_open_message_dialog(); + dlg->show("Loading precompiled shaders from disk...", type, [](s32 status) + { + if (status != CELL_OK) + Emu.Stop(); + }); + } + + void update_msg(u32 processed, u32 entry_count) override + { + dlg->progress_bar_set_message(0, fmt::format("Loading pipeline object %u of %u", processed, entry_count)); + owner->flip(0); + } + + void inc_value(u32 value) override + { + dlg->progress_bar_increment(0, (f32)value); + owner->flip(0); + } + + void close() override + { + dlg->return_code = CELL_OK; + dlg->close(); + } + } + helper(this); + + m_frame->enable_wm_event_queue(); + m_shaders_cache->load(&helper); + } } + void GLGSRender::on_exit() { m_prog_buffer.clear(); @@ -826,6 +879,7 @@ void GLGSRender::on_exit() m_text_printer.close(); m_gl_texture_cache.destroy(); m_depth_converter.destroy(); + m_ui_renderer.destroy(); for (u32 i = 0; i < occlusion_query_count; ++i) { @@ -963,8 +1017,22 @@ void GLGSRender::load_program(u32 vertex_base, u32 vertex_count) m_program->use(); if (m_prog_buffer.check_cache_missed()) + { m_shaders_cache->store(pipeline_properties, vertex_program, fragment_program); + //Notify the user with HUD notification + if (!m_custom_ui) + { + //Create notification but do not draw it at this time. No need to spam flip requests + m_custom_ui = std::make_unique(); + } + else if (auto casted = dynamic_cast(m_custom_ui.get())) + { + //Probe the notification + casted->touch(); + } + } + u8 *buf; u32 vertex_state_offset; u32 vertex_constants_offset; @@ -1244,6 +1312,13 @@ void GLGSRender::flip(int buffer) gl::screen.clear(gl::buffers::color); m_flip_fbo.blit(gl::screen, screen_area, areai(aspect_ratio).flipped_vertical(), gl::buffers::color, gl::filter::linear); + if (m_custom_ui) + { + gl::screen.bind(); + glViewport(0, 0, m_frame->client_width(), m_frame->client_height()); + m_ui_renderer.run(m_frame->client_width(), m_frame->client_height(), 0, *m_custom_ui.get()); + } + if (g_cfg.video.overlay) { gl::screen.bind(); @@ -1334,7 +1409,7 @@ void GLGSRender::on_notify_memory_unmapped(u32 address_base, u32 size) } } -void GLGSRender::do_local_task() +void GLGSRender::do_local_task(bool idle) { m_frame->clear_wm_events(); @@ -1354,6 +1429,15 @@ void GLGSRender::do_local_task() lock.unlock(); q.cv.notify_one(); } + + if (m_custom_ui) + { + if (!in_begin_end && idle && native_ui_flip_request.load()) + { + native_ui_flip_request.store(false); + flip((s32)current_display_buffer); + } + } } work_item& GLGSRender::post_flush_request(u32 address, gl::texture_cache::thrashed_set& flush_data) @@ -1415,4 +1499,9 @@ void GLGSRender::get_occlusion_query_result(rsx::occlusion_query_info* query) glGetQueryObjectiv((GLuint)query->driver_handle, GL_QUERY_RESULT, &result); query->result += result; +} + +void GLGSRender::shell_do_cleanup() +{ + m_ui_renderer.remove_temp_resources(); } \ No newline at end of file diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 8c3cac44bc..f99313fae7 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -294,6 +294,7 @@ private: gl::text_writer m_text_printer; gl::depth_convert_pass m_depth_converter; + gl::ui_overlay_renderer m_ui_renderer; std::mutex queue_guard; std::list work_queue; @@ -361,12 +362,14 @@ protected: void flip(int buffer) override; u64 timestamp() const override; - void do_local_task() override; + void do_local_task(bool idle) override; bool on_access_violation(u32 address, bool is_writing) override; void on_notify_memory_unmapped(u32 address_base, u32 size) override; void notify_tile_unbound(u32 tile) override; - virtual std::array, 4> copy_render_targets_to_memory() override; - virtual std::array, 2> copy_depth_stencil_buffer_to_memory() override; + std::array, 4> copy_render_targets_to_memory() override; + std::array, 2> copy_depth_stencil_buffer_to_memory() override; + + void shell_do_cleanup() override; }; diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.cpp b/rpcs3/Emu/RSX/GL/GLHelpers.cpp index b5e4f08996..e892f72c31 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.cpp +++ b/rpcs3/Emu/RSX/GL/GLHelpers.cpp @@ -557,4 +557,9 @@ namespace gl fmt::throw_exception("unknown primitive type" HERE); } } + + attrib_t vao::operator[](u32 index) const noexcept + { + return attrib_t(index); + } } diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.h b/rpcs3/Emu/RSX/GL/GLHelpers.h index d687a1be3d..4884a76df1 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.h +++ b/rpcs3/Emu/RSX/GL/GLHelpers.h @@ -58,6 +58,8 @@ namespace gl void enable_debugging(); capabilities& get_driver_caps(); + bool is_primitive_native(rsx::primitive_type in); + GLenum draw_mode(rsx::primitive_type in); class exception : public std::exception { @@ -489,6 +491,7 @@ namespace gl }; class vao; + class attrib_t; class buffer_pointer { @@ -1167,6 +1170,43 @@ namespace gl { return{ (vao*)this }; } + + attrib_t operator [] (u32 index) const noexcept; + }; + + class attrib_t + { + GLint m_location; + + public: + attrib_t(GLint location) + : m_location(location) + { + } + + GLint location() const + { + return m_location; + } + + void operator = (float rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs); } + void operator = (double rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs); } + + void operator = (const color1f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs.r); } + void operator = (const color1d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs.r); } + void operator = (const color2f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2f(location(), rhs.r, rhs.g); } + void operator = (const color2d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2d(location(), rhs.r, rhs.g); } + void operator = (const color3f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3f(location(), rhs.r, rhs.g, rhs.b); } + void operator = (const color3d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3d(location(), rhs.r, rhs.g, rhs.b); } + void operator = (const color4f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4f(location(), rhs.r, rhs.g, rhs.b, rhs.a); } + void operator = (const color4d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4d(location(), rhs.r, rhs.g, rhs.b, rhs.a); } + + void operator = (buffer_pointer& pointer) const + { + pointer.get_vao().enable_for_attribute(m_location); + glVertexAttribPointer(location(), pointer.size(), (GLenum)pointer.get_type(), pointer.normalize(), + pointer.stride(), (const void*)(size_t)pointer.offset()); + } }; class texture_view; @@ -1221,8 +1261,7 @@ namespace gl enum class format { - red = GL_RED, - r = GL_R, + r = GL_RED, rg = GL_RG, rgb = GL_RGB, rgba = GL_RGBA, @@ -1629,9 +1668,9 @@ namespace gl copy_from(nullptr, format, type, pixel_settings); } - void copy_from(void* dst, texture::format format, texture::type type) + void copy_from(void* src, texture::format format, texture::type type) { - copy_from(dst, format, type, pixel_unpack_settings()); + copy_from(src, format, type, pixel_unpack_settings()); } void copy_from(const buffer& buf, texture::format format, texture::type type) @@ -1872,9 +1911,6 @@ namespace gl settings& border_color(color4f value); }; - GLenum draw_mode(rsx::primitive_type in); - bool is_primitive_native(rsx::primitive_type in); - enum class indices_type { ubyte = GL_UNSIGNED_BYTE, @@ -2270,43 +2306,8 @@ namespace gl void operator = (const color3f& rhs) const { glProgramUniform3f(m_program.id(), location(), rhs.r, rhs.g, rhs.b); } void operator = (const color4i& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } void operator = (const color4f& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } - }; - - class attrib_t - { - GLuint m_program; - GLint m_location; - - public: - attrib_t(GLuint program, GLint location) - : m_program(program) - , m_location(location) - { - } - - GLint location() const - { - return m_location; - } - - void operator = (float rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs); } - void operator = (double rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs); } - - void operator = (const color1f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs.r); } - void operator = (const color1d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs.r); } - void operator = (const color2f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2f(location(), rhs.r, rhs.g); } - void operator = (const color2d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2d(location(), rhs.r, rhs.g); } - void operator = (const color3f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3f(location(), rhs.r, rhs.g, rhs.b); } - void operator = (const color3d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3d(location(), rhs.r, rhs.g, rhs.b); } - void operator = (const color4f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4f(location(), rhs.r, rhs.g, rhs.b, rhs.a); } - void operator = (const color4d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4d(location(), rhs.r, rhs.g, rhs.b, rhs.a); } - - void operator =(buffer_pointer& pointer) const - { - pointer.get_vao().enable_for_attribute(location()); - glVertexAttribPointer(location(), pointer.size(), (GLenum)pointer.get_type(), pointer.normalize(), - pointer.stride(), (const void*)(size_t)pointer.offset()); - } + void operator = (const areaf& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } + void operator = (const areai& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } }; class uniforms_t @@ -2471,12 +2472,12 @@ namespace gl attrib_t operator[](GLint location) { - return{ m_program.id(), location }; + return{ location }; } attrib_t operator[](const std::string &name) { - return{ m_program.id(), location(name) }; + return{ location(name) }; } void swap(attribs_t& attribs) diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.h b/rpcs3/Emu/RSX/GL/GLOverlays.h index 451a5aaac6..428a15a867 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.h +++ b/rpcs3/Emu/RSX/GL/GLOverlays.h @@ -2,6 +2,9 @@ #include "stdafx.h" #include "GLHelpers.h" +#include "../overlays.h" + +extern u64 get_system_time(); namespace gl { @@ -16,8 +19,14 @@ namespace gl gl::fbo fbo; + gl::vao m_vao; + gl::buffer m_vertex_data_buffer; + bool compiled = false; + u32 num_drawable_elements = 4; + GLenum primitives = GL_TRIANGLE_STRIP; + void create() { if (!compiled) @@ -37,6 +46,20 @@ namespace gl fbo.create(); + m_vertex_data_buffer.create(); + + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.create(); + m_vao.bind(); + + m_vao.array_buffer = m_vertex_data_buffer; + auto ptr = buffer_pointer(&m_vao); + m_vao[0] = ptr; + + glBindVertexArray(old_vao); + compiled = true; } } @@ -50,6 +73,8 @@ namespace gl fs.remove(); fbo.remove(); + m_vao.remove(); + m_vertex_data_buffer.remove(); compiled = false; } @@ -61,12 +86,24 @@ namespace gl virtual void bind_resources() {} virtual void cleanup_resources() {} - virtual void emit_geometry() + virtual void upload_vertex_data(f32* data, u32 elements_count) { - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + elements_count <<= 2; + m_vertex_data_buffer.data(elements_count, data); } - virtual void run(u16 w, u16 h, GLuint target_texture, bool depth_target) + virtual void emit_geometry() + { + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.bind(); + glDrawArrays(primitives, 0, num_drawable_elements); + + glBindVertexArray(old_vao); + } + + virtual void run(u16 w, u16 h, GLuint target_texture, bool depth_target, bool use_blending = false) { if (!compiled) { @@ -81,22 +118,32 @@ namespace gl GLboolean color_writes[4]; GLboolean depth_write; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo.id()); + GLint blend_src_rgb; + GLint blend_src_a; + GLint blend_dst_rgb; + GLint blend_dst_a; + GLint blend_eq_a; + GLint blend_eq_rgb; - if (depth_target) + if (target_texture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target_texture, 0); - glDrawBuffer(GL_NONE); - } - else - { - GLenum buffer = GL_COLOR_ATTACHMENT0; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_texture, 0); - glDrawBuffers(1, &buffer); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo.id()); + + if (depth_target) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target_texture, 0); + glDrawBuffer(GL_NONE); + } + else + { + GLenum buffer = GL_COLOR_ATTACHMENT0; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_texture, 0); + glDrawBuffers(1, &buffer); + } } - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + if (!target_texture || glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { // Push rasterizer state glGetIntegerv(GL_VIEWPORT, viewport); @@ -111,6 +158,16 @@ namespace gl GLboolean blend_enabled = glIsEnabled(GL_BLEND); GLboolean stencil_test_enabled = glIsEnabled(GL_STENCIL_TEST); + if (use_blending) + { + glGetIntegerv(GL_BLEND_SRC_RGB, &blend_src_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &blend_src_a); + glGetIntegerv(GL_BLEND_DST_RGB, &blend_dst_rgb); + glGetIntegerv(GL_BLEND_DST_ALPHA, &blend_dst_a); + glGetIntegerv(GL_BLEND_EQUATION_RGB, &blend_eq_rgb); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blend_eq_a); + } + // Set initial state glViewport(0, 0, w, h); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); @@ -122,9 +179,21 @@ namespace gl if (scissor_enabled) glDisable(GL_SCISSOR_TEST); if (cull_face_enabled) glDisable(GL_CULL_FACE); - if (blend_enabled) glDisable(GL_BLEND); if (stencil_test_enabled) glDisable(GL_STENCIL_TEST); + if (use_blending) + { + if (!blend_enabled) + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + } + else if (blend_enabled) + { + glDisable(GL_BLEND); + } + // Render program_handle.use(); on_load(); @@ -132,12 +201,16 @@ namespace gl emit_geometry(); // Clean up - if (depth_target) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - else - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + if (target_texture) + { + if (depth_target) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); + } - glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); glUseProgram((GLuint)program); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); @@ -148,8 +221,20 @@ namespace gl if (!depth_test_enabled) glDisable(GL_DEPTH_TEST); if (scissor_enabled) glEnable(GL_SCISSOR_TEST); if (cull_face_enabled) glEnable(GL_CULL_FACE); - if (blend_enabled) glEnable(GL_BLEND); if (stencil_test_enabled) glEnable(GL_STENCIL_TEST); + + if (use_blending) + { + if (!blend_enabled) + glDisable(GL_BLEND); + + glBlendFuncSeparate(blend_src_rgb, blend_dst_rgb, blend_src_a, blend_dst_a); + glBlendEquationSeparate(blend_eq_rgb, blend_eq_a); + } + else if (blend_enabled) + { + glEnable(GL_BLEND); + } } else { @@ -237,4 +322,251 @@ namespace gl overlay_pass::run(w, h, target, false); } }; -} \ No newline at end of file + + struct ui_overlay_renderer : public overlay_pass + { + u32 num_elements = 0; + std::vector> resources; + std::unordered_map> temp_image_cache; + std::unordered_map> font_cache; + bool is_font_draw = false; + + ui_overlay_renderer() + { + vs_src = + { + "#version 420\n\n" + "layout(location=0) in vec4 in_pos;\n" + "layout(location=0) out vec2 tc0;\n" + "layout(location=1) out vec4 clip_rect;\n" + "uniform vec4 ui_scale;\n" + "uniform vec4 clip_bounds;\n" + "\n" + "void main()\n" + "{\n" + " tc0.xy = in_pos.zw;\n" + " clip_rect = (clip_bounds * ui_scale.zwzw);\n" + " clip_rect.yw = ui_scale.yy - clip_rect.wy; //invert y axis\n" + " vec4 pos = vec4((in_pos.xy * ui_scale.zw) / ui_scale.xy, 0., 1.);\n" + " pos.y = (1. - pos.y); //invert y axis\n" + " gl_Position = (pos + pos) - 1.;\n" + "}\n" + }; + + fs_src = + { + "#version 420\n\n" + "layout(binding=31) uniform sampler2D fs0;\n" + "layout(location=0) in vec2 tc0;\n" + "layout(location=1) in vec4 clip_rect;\n" + "layout(location=0) out vec4 ocol;\n" + "uniform vec4 color;\n" + "uniform float time;\n" + "uniform int read_texture;\n" + "uniform int pulse_glow;\n" + "uniform int clip_region;\n" + "\n" + "void main()\n" + "{\n" + " if (clip_region != 0)\n" + " {" + " if (gl_FragCoord.x < clip_rect.x || gl_FragCoord.x > clip_rect.z ||\n" + " gl_FragCoord.y < clip_rect.y || gl_FragCoord.y > clip_rect.w)\n" + " {\n" + " discard;\n" + " return;\n" + " }\n" + " }\n" + "\n" + " vec4 diff_color = color;\n" + " if (pulse_glow != 0)\n" + " diff_color.a *= (sin(time) + 1.f) * 0.5f;\n" + "\n" + " if (read_texture != 0)\n" + " ocol = texture(fs0, tc0) * diff_color;\n" + " else\n" + " ocol = diff_color;\n" + "}\n" + }; + } + + gl::texture* load_simple_image(rsx::overlays::image_info* desc, bool temp_resource) + { + auto tex = std::make_unique(gl::texture::target::texture2D); + tex->create(); + tex->config() + .size({ desc->w, desc->h }) + .format(gl::texture::format::rgba) + .type(gl::texture::type::uint_8_8_8_8) + .wrap(gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border) + .swizzle(gl::texture::channel::a, gl::texture::channel::b, gl::texture::channel::g, gl::texture::channel::r) + .filter(gl::min_filter::linear, gl::filter::linear) + .apply(); + tex->copy_from(desc->data, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8); + + if (!temp_resource) + { + resources.push_back(std::move(tex)); + } + else + { + u64 key = (u64)desc; + temp_image_cache[key] = std::move(tex); + } + + return resources.back().get(); + } + + void create() + { + overlay_pass::create(); + + rsx::overlays::resource_config configuration; + configuration.load_files(); + + for (const auto &res : configuration.texture_raw_data) + { + load_simple_image(res.get(), false); + } + + configuration.free_resources(); + } + + void destroy() + { + temp_image_cache.clear(); + resources.clear(); + font_cache.clear(); + overlay_pass::destroy(); + } + + void remove_temp_resources() + { + temp_image_cache.clear(); + } + + gl::texture* find_font(rsx::overlays::font *font) + { + u64 key = (u64)font; + auto found = font_cache.find(key); + if (found != font_cache.end()) + return found->second.get(); + + //Create font file + auto tex = std::make_unique(gl::texture::target::texture2D); + tex->create(); + tex->config() + .size({ (int)font->width, (int)font->height }) + .format(gl::texture::format::r) + .type(gl::texture::type::ubyte) + .internal_format(gl::texture::internal_format::r8) + .wrap(gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border) + .swizzle(gl::texture::channel::r, gl::texture::channel::r, gl::texture::channel::r, gl::texture::channel::r) + .filter(gl::min_filter::linear, gl::filter::linear) + .apply(); + tex->copy_from(font->glyph_data.data(), gl::texture::format::r, gl::texture::type::ubyte); + + auto result = tex.get(); + font_cache[key] = std::move(tex); + + return result; + } + + gl::texture* find_temp_image(rsx::overlays::image_info *desc) + { + auto key = (u64)desc; + auto cached = temp_image_cache.find(key); + if (cached != temp_image_cache.end()) + { + return cached->second.get(); + } + else + { + return load_simple_image(desc, true); + } + } + + void emit_geometry() override + { + if (!is_font_draw) + { + overlay_pass::emit_geometry(); + } + else + { + int num_quads = num_drawable_elements / 4; + std::vector firsts; + std::vector counts; + + firsts.resize(num_quads); + counts.resize(num_quads); + + for (int n = 0; n < num_quads; ++n) + { + firsts[n] = (n * 4); + counts[n] = 4; + } + + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.bind(); + glMultiDrawArrays(GL_TRIANGLE_STRIP, firsts.data(), counts.data(), num_quads); + + glBindVertexArray(old_vao); + } + } + + void run(u16 w, u16 h, GLuint target, rsx::overlays::user_interface& ui) + { + program_handle.uniforms["ui_scale"] = color4f((f32)ui.virtual_width, (f32)ui.virtual_height, 1.f, 1.f); + program_handle.uniforms["time"] = (f32)(get_system_time() / 1000) * 0.005f; + for (auto &cmd : ui.get_compiled().draw_commands) + { + upload_vertex_data((f32*)cmd.second.data(), (u32)cmd.second.size() * 4u); + num_drawable_elements = (u32)cmd.second.size(); + is_font_draw = false; + GLint texture_exists = GL_TRUE; + + glActiveTexture(GL_TEXTURE31); + switch (cmd.first.texture_ref) + { + case rsx::overlays::image_resource_id::game_icon: + case rsx::overlays::image_resource_id::backbuffer: + //TODO + case rsx::overlays::image_resource_id::none: + { + texture_exists = GL_FALSE; + glBindTexture(GL_TEXTURE_2D, GL_NONE); + break; + } + case rsx::overlays::image_resource_id::raw_image: + { + glBindTexture(GL_TEXTURE_2D, find_temp_image((rsx::overlays::image_info*)cmd.first.external_data_ref)->id()); + break; + } + case rsx::overlays::image_resource_id::font_file: + { + is_font_draw = true; + glBindTexture(GL_TEXTURE_2D, find_font(cmd.first.font_ref)->id()); + break; + } + default: + { + glBindTexture(GL_TEXTURE_2D, resources[cmd.first.texture_ref - 1]->id()); + break; + } + } + + program_handle.uniforms["color"] = cmd.first.color; + program_handle.uniforms["read_texture"] = texture_exists; + program_handle.uniforms["pulse_glow"] = (s32)cmd.first.pulse_glow; + program_handle.uniforms["clip_region"] = (s32)cmd.first.clip_region; + program_handle.uniforms["clip_bounds"] = cmd.first.clip_rect; + overlay_pass::run(w, h, target, false, true); + } + + ui.update(); + } + }; +} diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index 10e00a58d1..700d2c8ee1 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -31,13 +31,13 @@ color_format rsx::internals::surface_color_format_to_gl(rsx::surface_color_forma return{ ::gl::texture::type::f32, ::gl::texture::format::rgba, true, 4, 4 }; case rsx::surface_color_format::b8: - return{ ::gl::texture::type::ubyte, ::gl::texture::format::red, false, 1, 1 }; + return{ ::gl::texture::type::ubyte, ::gl::texture::format::r, false, 1, 1 }; case rsx::surface_color_format::g8b8: return{ ::gl::texture::type::ubyte, ::gl::texture::format::rg, false, 2, 1 }; case rsx::surface_color_format::x32: - return{ ::gl::texture::type::f32, ::gl::texture::format::red, true, 1, 4 }; + return{ ::gl::texture::type::f32, ::gl::texture::format::r, true, 1, 4 }; default: LOG_ERROR(RSX, "Surface color buffer: Unsupported surface color format (0x%x)", (u32)color_format); diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index ca54ff8077..e028a43239 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -142,7 +142,6 @@ namespace gl switch (fmt_) { - case texture::format::red: case texture::format::r: break; case texture::format::rg: diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index bb65cb1bb4..ae86c25072 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -481,9 +481,6 @@ namespace rsx // TODO: exit condition while (!Emu.IsStopped()) { - //Execute backend-local tasks first - do_local_task(); - //Wait for external pause events if (external_interrupt_lock.load()) { @@ -491,6 +488,9 @@ namespace rsx while (external_interrupt_lock.load()) _mm_pause(); } + //Execute backend-local tasks first + do_local_task(ctrl->put.load() == internal_get.load()); + //Set up restore state if needed if (sync_point_request) { @@ -2179,7 +2179,13 @@ namespace rsx void thread::pause() { external_interrupt_lock.store(true); - while (!external_interrupt_ack.load()) _mm_pause(); + while (!external_interrupt_ack.load()) + { + if (Emu.IsStopped()) + break; + + _mm_pause(); + } external_interrupt_ack.store(false); } @@ -2187,4 +2193,67 @@ namespace rsx { external_interrupt_lock.store(false); } + + //TODO: Move these helpers into a better class dedicated to shell interface handling (use idm?) + //They are not dependent on rsx at all + rsx::overlays::save_dialog* thread::shell_open_save_dialog() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::save_dialog(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::message_dialog* thread::shell_open_message_dialog() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::message_dialog(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::trophy_notification* thread::shell_open_trophy_notification() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::trophy_notification(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::user_interface* thread::shell_get_current_dialog() + { + //TODO: Only get dialog type interfaces + return m_custom_ui.get(); + } + + bool thread::shell_close_dialog() + { + //TODO: Only get dialog type interfaces + if (m_custom_ui) + { + m_invalidated_ui = std::move(m_custom_ui); + shell_do_cleanup(); + return true; + } + + return false; + } } diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 36d3845c48..c836c469d8 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -12,6 +12,7 @@ #include "RSXFragmentProgram.h" #include "rsx_methods.h" #include "rsx_utils.h" +#include "overlays.h" #include #include "Utilities/Thread.h" @@ -216,6 +217,7 @@ namespace rsx bool skip_frame = false; bool supports_multidraw = false; + bool supports_native_ui = false; //occlusion query bool zcull_surface_active = false; @@ -230,6 +232,9 @@ namespace rsx rsx::gcm_framebuffer_info m_depth_surface_info; bool framebuffer_status_valid = false; + std::unique_ptr m_custom_ui; + std::unique_ptr m_invalidated_ui; + public: RsxDmaControl* ctrl = nullptr; atomic_t internal_get{ 0 }; @@ -237,6 +242,9 @@ namespace rsx atomic_t external_interrupt_lock{ false }; atomic_t external_interrupt_ack{ false }; + //native UI interrupts + atomic_t native_ui_flip_request{ false }; + GcmTileInfo tiles[limits::tiles_count]; GcmZcullInfo zculls[limits::zculls_count]; @@ -328,8 +336,9 @@ namespace rsx /** * Execute a backend local task queue + * Idle argument checks that the FIFO queue is in an idle state */ - virtual void do_local_task() {} + virtual void do_local_task(bool idle) {} public: virtual std::string get_name() const override; @@ -419,6 +428,8 @@ namespace rsx public: //std::future add_internal_task(std::function callback); //void invoke(std::function callback); + void add_user_interface(std::shared_ptr iface); + void remove_user_interface(); /** * Fill buffer with 4x4 scale offset matrix. @@ -484,5 +495,14 @@ namespace rsx void pause(); void unpause(); + + //HLE vsh stuff + //TODO: Move into a separate helper + virtual rsx::overlays::save_dialog* shell_open_save_dialog(); + virtual rsx::overlays::message_dialog* shell_open_message_dialog(); + virtual rsx::overlays::trophy_notification* shell_open_trophy_notification(); + virtual rsx::overlays::user_interface* shell_get_current_dialog(); + virtual bool shell_close_dialog(); + virtual void shell_do_cleanup(){} }; } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 2edccd8d43..8293750951 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -646,25 +646,30 @@ VKGSRender::VKGSRender() : GSRender() for (u32 i = 0; i < m_swap_chain->get_swap_image_count(); ++i) { + VkClearColorValue clear_color{}; + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + range); - VkClearColorValue clear_color{}; - auto range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + range); } m_current_frame = &frame_context_storage[0]; m_texture_cache.initialize((*m_device), m_memory_type_mapping, m_optimal_tiling_supported_formats, m_swap_chain->get_present_queue(), - m_texture_upload_buffer_ring_info, m_texture_upload_buffer_ring_info.heap.get()); + m_texture_upload_buffer_ring_info); + + m_ui_renderer.reset(new vk::ui_overlay_renderer()); + m_ui_renderer->create(*m_current_command_buffer, m_memory_type_mapping, m_texture_upload_buffer_ring_info); supports_multidraw = !g_cfg.video.strict_rendering_mode; + supports_native_ui = (bool)g_cfg.misc.use_native_interface; } VKGSRender::~VKGSRender() @@ -743,6 +748,10 @@ VKGSRender::~VKGSRender() //Overlay text handler m_text_writer.reset(); + //Overlay UI renderer + m_ui_renderer->destroy(); + m_ui_renderer.reset(); + //RGBA->depth cast helper m_depth_converter->destroy(); m_depth_converter.reset(); @@ -1597,13 +1606,63 @@ void VKGSRender::on_init_thread() GSRender::on_init_thread(); rsx_thread = std::this_thread::get_id(); - m_frame->disable_wm_event_queue(); - m_frame->hide(); + if (!supports_native_ui) + { + m_frame->disable_wm_event_queue(); + m_frame->hide(); + m_shaders_cache->load(nullptr, *m_device, pipeline_layout); + m_frame->enable_wm_event_queue(); + m_frame->show(); + } + else + { + struct native_helper : vk::shader_cache::progress_dialog_helper + { + rsx::thread *owner = nullptr; + rsx::overlays::message_dialog *dlg = nullptr; - m_shaders_cache->load(*m_device, pipeline_layout); + native_helper(VKGSRender *ptr) : + owner(ptr) {} - m_frame->enable_wm_event_queue(); - m_frame->show(); + void create() override + { + MsgDialogType type = {}; + type.disable_cancel = true; + type.progress_bar_count = 1; + + dlg = owner->shell_open_message_dialog(); + dlg->show("Loading precompiled shaders from disk...", type, [](s32 status) + { + if (status != CELL_OK) + Emu.Stop(); + }); + } + + void update_msg(u32 processed, u32 entry_count) override + { + dlg->progress_bar_set_message(0, fmt::format("Loading pipeline object %u of %u", processed, entry_count)); + owner->flip(0); + } + + void inc_value(u32 value) override + { + dlg->progress_bar_increment(0, (f32)value); + owner->flip(0); + } + + void close() override + { + dlg->return_code = CELL_OK; + dlg->close(); + } + } + helper(this); + + //TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support + m_frame->disable_wm_event_queue(); + m_shaders_cache->load(&helper, *m_device, pipeline_layout); + m_frame->enable_wm_event_queue(); + } } void VKGSRender::on_exit() @@ -1951,6 +2010,7 @@ void VKGSRender::process_swap_request(frame_context_t *ctx, bool free_resources) } m_depth_converter->free_resources(); + m_ui_renderer->free_resources(); ctx->buffer_views_to_clean.clear(); ctx->samplers_to_clean.clear(); @@ -1975,8 +2035,16 @@ void VKGSRender::process_swap_request(frame_context_t *ctx, bool free_resources) ctx->swap_command_buffer = nullptr; } -void VKGSRender::do_local_task() +void VKGSRender::do_local_task(bool idle) { + //TODO: Guard this + if (m_overlay_cleanup_requests.size()) + { + flush_command_queue(true); + m_ui_renderer->remove_temp_resources(); + m_overlay_cleanup_requests.clear(); + } + if (m_flush_commands) { std::lock_guard lock(m_flush_queue_mutex); @@ -2082,6 +2150,16 @@ void VKGSRender::do_local_task() } #endif + + if (m_custom_ui) + { + if (!in_begin_end && native_ui_flip_request.load()) + { + native_ui_flip_request.store(false); + flush_command_queue(true); + flip((s32)current_display_buffer); + } + } } bool VKGSRender::do_method(u32 cmd, u32 arg) @@ -2255,8 +2333,22 @@ void VKGSRender::load_program(u32 vertex_count, u32 vertex_base) m_program = m_prog_buffer->getGraphicPipelineState(vertex_program, fragment_program, properties, *m_device, pipeline_layout).get(); if (m_prog_buffer->check_cache_missed()) + { m_shaders_cache->store(properties, vertex_program, fragment_program); + //Notify the user with HUD notification + if (!m_custom_ui) + { + //Create notification but do not draw it at this time. No need to spam flip requests + m_custom_ui = std::make_unique(); + } + else if (auto casted = dynamic_cast(m_custom_ui.get())) + { + //Probe the notification + casted->touch(); + } + } + vk::leave_uninterruptible(); const size_t fragment_constants_sz = m_prog_buffer->get_fragment_constants_buffer_size(fragment_program); @@ -2740,14 +2832,14 @@ void VKGSRender::reinitialize_swapchain() { vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VkClearColorValue clear_color{}; auto range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } //Will have to block until rendering is completed @@ -2827,9 +2919,6 @@ void VKGSRender::flip(int buffer) u32 buffer_width = display_buffers[buffer].width; u32 buffer_height = display_buffers[buffer].height; - u32 buffer_pitch = display_buffers[buffer].pitch; - - areai screen_area = coordi({}, { (int)buffer_width, (int)buffer_height }); coordi aspect_ratio; @@ -2905,22 +2994,49 @@ void VKGSRender::flip(int buffer) //Blit contents to screen.. vk::image* image_to_flip = nullptr; - if (std::get<1>(m_rtts.m_bound_render_targets[0]) != nullptr) - image_to_flip = std::get<1>(m_rtts.m_bound_render_targets[0]); - else if (std::get<1>(m_rtts.m_bound_render_targets[1]) != nullptr) - image_to_flip = std::get<1>(m_rtts.m_bound_render_targets[1]); + rsx::tiled_region buffer_region = get_tiled_address(display_buffers[buffer].offset, CELL_GCM_LOCATION_LOCAL); + u32 absolute_address = buffer_region.address + buffer_region.base; + + if (auto render_target_texture = m_rtts.get_texture_from_render_target_if_applicable(absolute_address)) + { + image_to_flip = render_target_texture; + } + else if (auto surface = m_texture_cache.find_texture_from_dimensions(absolute_address)) + { + //Hack - this should be the first location to check for output + //The render might have been done offscreen or in software and a blit used to display + image_to_flip = surface->get_raw_texture(); + } VkImage target_image = m_swap_chain->get_swap_chain_image(m_current_frame->present_image); if (image_to_flip) { - vk::copy_scaled_image(*m_current_command_buffer, image_to_flip->value, target_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VkImageLayout target_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + if (aspect_ratio.x) + { + VkClearColorValue clear_black {}; + vk::change_image_layout(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, range); + vkCmdClearColorImage(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_black, 1, &range); + + target_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + } + + vk::copy_scaled_image(*m_current_command_buffer, image_to_flip->value, target_image, image_to_flip->current_layout, target_layout, 0, 0, image_to_flip->width(), image_to_flip->height(), aspect_ratio.x, aspect_ratio.y, aspect_ratio.width, aspect_ratio.height, 1, VK_IMAGE_ASPECT_COLOR_BIT, false); + + if (target_layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) + { + vk::change_image_layout(*m_current_command_buffer, target_image, target_layout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, range); + } } else { //No draw call was issued! - VkImageSubresourceRange range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); - VkClearColorValue clear_black = { 0 }; + //TODO: Upload raw bytes from cpu for rendering + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + VkClearColorValue clear_black {}; vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_GENERAL, range); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_GENERAL, &clear_black, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, range); @@ -2928,10 +3044,10 @@ void VKGSRender::flip(int buffer) std::unique_ptr direct_fbo; std::vector> swap_image_view; - if (g_cfg.video.overlay) + if (g_cfg.video.overlay || m_custom_ui) { //Change the image layout whilst setting up a dependency on waiting for the blit op to finish before we start writing - auto subres = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); + VkImageSubresourceRange subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; @@ -2966,19 +3082,27 @@ void VKGSRender::flip(int buffer) direct_fbo.reset(new vk::framebuffer_holder(*m_device, single_target_pass, m_client_width, m_client_height, std::move(swap_image_view))); } - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 0, direct_fbo->width(), direct_fbo->height(), "draw calls: " + std::to_string(m_draw_calls)); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 18, direct_fbo->width(), direct_fbo->height(), "draw call setup: " + std::to_string(m_setup_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 36, direct_fbo->width(), direct_fbo->height(), "vertex upload time: " + std::to_string(m_vertex_upload_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 54, direct_fbo->width(), direct_fbo->height(), "texture upload time: " + std::to_string(m_textures_upload_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 72, direct_fbo->width(), direct_fbo->height(), "draw call execution: " + std::to_string(m_draw_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 90, direct_fbo->width(), direct_fbo->height(), "submit and flip: " + std::to_string(m_flip_time) + "us"); + if (m_custom_ui) + { + m_ui_renderer->run(*m_current_command_buffer, direct_fbo->width(), direct_fbo->height(), direct_fbo.get(), single_target_pass, m_memory_type_mapping, m_texture_upload_buffer_ring_info, *m_custom_ui); + } - auto num_dirty_textures = m_texture_cache.get_unreleased_textures_count(); - auto texture_memory_size = m_texture_cache.get_texture_memory_in_use() / (1024 * 1024); - auto tmp_texture_memory_size = m_texture_cache.get_temporary_memory_in_use() / (1024 * 1024); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 126, direct_fbo->width(), direct_fbo->height(), "Unreleased textures: " + std::to_string(num_dirty_textures)); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 144, direct_fbo->width(), direct_fbo->height(), "Texture cache memory: " + std::to_string(texture_memory_size) + "M"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 162, direct_fbo->width(), direct_fbo->height(), "Temporary texture memory: " + std::to_string(tmp_texture_memory_size) + "M"); + if (g_cfg.video.overlay) + { + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 0, direct_fbo->width(), direct_fbo->height(), "draw calls: " + std::to_string(m_draw_calls)); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 18, direct_fbo->width(), direct_fbo->height(), "draw call setup: " + std::to_string(m_setup_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 36, direct_fbo->width(), direct_fbo->height(), "vertex upload time: " + std::to_string(m_vertex_upload_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 54, direct_fbo->width(), direct_fbo->height(), "texture upload time: " + std::to_string(m_textures_upload_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 72, direct_fbo->width(), direct_fbo->height(), "draw call execution: " + std::to_string(m_draw_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 90, direct_fbo->width(), direct_fbo->height(), "submit and flip: " + std::to_string(m_flip_time) + "us"); + + auto num_dirty_textures = m_texture_cache.get_unreleased_textures_count(); + auto texture_memory_size = m_texture_cache.get_texture_memory_in_use() / (1024 * 1024); + auto tmp_texture_memory_size = m_texture_cache.get_temporary_memory_in_use() / (1024 * 1024); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 126, direct_fbo->width(), direct_fbo->height(), "Unreleased textures: " + std::to_string(num_dirty_textures)); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 144, direct_fbo->width(), direct_fbo->height(), "Texture cache memory: " + std::to_string(texture_memory_size) + "M"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 162, direct_fbo->width(), direct_fbo->height(), "Temporary texture memory: " + std::to_string(tmp_texture_memory_size) + "M"); + } vk::change_image_layout(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, subres); m_framebuffers_to_clean.push_back(std::move(direct_fbo)); @@ -3088,4 +3212,10 @@ void VKGSRender::get_occlusion_query_result(rsx::occlusion_query_info* query) m_occlusion_query_pool.reset_queries(*m_current_command_buffer, data.indices); m_occlusion_map.erase(query->driver_handle); +} + +void VKGSRender::shell_do_cleanup() +{ + //TODO: Guard this + m_overlay_cleanup_requests.push_back(0); } \ No newline at end of file diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index e7e054f85a..976e02306a 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -136,6 +136,7 @@ private: std::unique_ptr m_text_writer; std::unique_ptr m_depth_converter; + std::unique_ptr m_ui_renderer; std::mutex m_sampler_mutex; u64 surface_store_tag = 0; @@ -287,6 +288,8 @@ private: //Vertex layout rsx::vertex_input_layout m_vertex_layout; + + std::vector m_overlay_cleanup_requests; #if !defined(_WIN32) && defined(HAVE_VULKAN) Display *m_display_handle = nullptr; @@ -341,10 +344,12 @@ protected: bool do_method(u32 id, u32 arg) override; void flip(int buffer) override; - void do_local_task() override; + void do_local_task(bool idle) override; bool scaled_image_from_memory(rsx::blit_src_info& src, rsx::blit_dst_info& dst, bool interpolate) override; void notify_tile_unbound(u32 tile) override; bool on_access_violation(u32 address, bool is_writing) override; void on_notify_memory_unmapped(u32 address_base, u32 size) override; + + void shell_do_cleanup() override; }; diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index 7b18ae35b0..f0400d2188 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -455,7 +455,7 @@ namespace vk struct image_view { - VkImageView value; + VkImageView value = VK_NULL_HANDLE; VkImageViewCreateInfo info = {}; image_view(VkDevice dev, VkImage image, VkImageViewType view_type, VkFormat format, VkComponentMapping mapping, VkImageSubresourceRange range) @@ -477,6 +477,34 @@ namespace vk CHECK_RESULT(vkCreateImageView(m_device, &info, nullptr, &value)); } + image_view(VkDevice dev, vk::image* resource, VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, VkComponentMapping mapping = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }) + : m_device(dev) + { + info.format = resource->info.format; + info.image = resource->value; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.components = mapping; + info.subresourceRange = range; + + switch (resource->info.imageType) + { + case VK_IMAGE_TYPE_1D: + info.viewType = VK_IMAGE_VIEW_TYPE_1D; + break; + case VK_IMAGE_TYPE_2D: + if (resource->info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) + info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + else + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + break; + case VK_IMAGE_TYPE_3D: + info.viewType = VK_IMAGE_VIEW_TYPE_3D; + break; + } + + CHECK_RESULT(vkCreateImageView(m_device, &info, nullptr, &value)); + } + ~image_view() { vkDestroyImageView(m_device, value, nullptr); @@ -1181,7 +1209,7 @@ namespace vk m_instance = nullptr; //Check that some critical entry-points have been loaded into memory indicating prescence of a loader - loader_exists = (&vkCreateInstance != nullptr); + loader_exists = (vkCreateInstance != nullptr); } ~context() @@ -1279,8 +1307,8 @@ namespace vk instance_info.pApplicationInfo = &app; instance_info.enabledLayerCount = static_cast(layers.size()); instance_info.ppEnabledLayerNames = layers.data(); - instance_info.enabledExtensionCount = fast? 0: static_cast(extensions.size()); - instance_info.ppEnabledExtensionNames = fast? nullptr: extensions.data(); + instance_info.enabledExtensionCount = fast ? 0 : static_cast(extensions.size()); + instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data(); VkInstance instance; if (vkCreateInstance(&instance_info, nullptr, &instance) != VK_SUCCESS) @@ -1704,5 +1732,5 @@ namespace vk */ void copy_mipmaped_image_using_buffer(VkCommandBuffer cmd, VkImage dst_image, const std::vector& subresource_layout, int format, bool is_swizzled, u16 mipmap_count, - VkImageAspectFlags flags, vk::vk_data_heap &upload_heap, vk::buffer* upload_buffer); + VkImageAspectFlags flags, vk::vk_data_heap &upload_heap); } diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index 23cee8087e..dec6017f7f 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -4,6 +4,8 @@ #include "VKFragmentProgram.h" #include "VKRenderTargets.h" +#include "../overlays.h" + namespace vk { //TODO: Refactor text print class to inherit from this base class @@ -21,6 +23,8 @@ namespace vk std::unordered_map> m_program_cache; std::unique_ptr m_sampler; std::unique_ptr m_draw_fbo; + std::unique_ptr m_vao; + std::unique_ptr m_ubo; vk::render_device* m_device = nullptr; std::string vs_src; @@ -32,33 +36,43 @@ namespace vk bool write_color = true; bool write_depth = true; bool no_depth_test = true; + bool enable_blend = false; } renderpass_config; bool initialized = false; bool compiled = false; + u32 num_drawable_elements = 4; + u32 first_vertex = 0; + void init_descriptors() { - VkDescriptorPoolSize descriptor_pools[1] = + VkDescriptorPoolSize descriptor_pool_sizes[2] = { { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 120 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 120 }, }; //Reserve descriptor pools - m_descriptor_pool.create(*m_device, descriptor_pools, 1); + m_descriptor_pool.create(*m_device, descriptor_pool_sizes, 2); - VkDescriptorSetLayoutBinding bindings[1] = {}; + VkDescriptorSetLayoutBinding bindings[2] = {}; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[0].descriptorCount = 1; bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[0].binding = 0; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[1].binding = 1; + VkDescriptorSetLayoutCreateInfo infos = {}; infos.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; infos.pBindings = bindings; - infos.bindingCount = 1; + infos.bindingCount = 2; CHECK_RESULT(vkCreateDescriptorSetLayout(*m_device, &infos, nullptr, &m_descriptor_layout)); @@ -70,6 +84,45 @@ namespace vk CHECK_RESULT(vkCreatePipelineLayout(*m_device, &layout_info, nullptr, &m_pipeline_layout)); } + virtual void update_uniforms(vk::glsl::program *program) + { + } + + virtual std::vector get_vertex_inputs() + { + if (!m_vao) + { + auto memory_types = vk::get_memory_mapping(m_device->gpu()); + m_vao = std::make_unique(*m_device, 1 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0); + m_ubo = std::make_unique(*m_device, 8 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0); + } + + return{}; + } + + virtual std::vector get_fragment_inputs() + { + std::vector fs_inputs; + fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_texture,{},{}, 0, "fs0" }); + fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_uniform_buffer,{},{}, 1, "static_data" }); + return fs_inputs; + } + + void upload_vertex_data(f32 *data, u32 first, u32 count) + { + verify(HERE), (first + count) <= 65536; + if (!m_vao) + { + auto memory_types = vk::get_memory_mapping(m_device->gpu()); + m_vao = std::make_unique(*m_device, 1 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0); + m_ubo = std::make_unique(*m_device, 8 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0); + } + + auto dst = m_vao->map((first * 4), VK_WHOLE_SIZE); + std::memcpy(dst, data, count * sizeof(f32)); + m_vao->unmap(); + } + vk::glsl::program* build_pipeline(VkRenderPass render_pass) { if (!compiled) @@ -101,8 +154,14 @@ namespace vk dynamic_state_descriptors[dynamic_state_info.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; dynamic_state_info.pDynamicStates = dynamic_state_descriptors; + VkVertexInputBindingDescription vb = { 0, 16, VK_VERTEX_INPUT_RATE_VERTEX }; + VkVertexInputAttributeDescription via = { 0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0 }; VkPipelineVertexInputStateCreateInfo vi = {}; vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vi.vertexBindingDescriptionCount = 1; + vi.pVertexBindingDescriptions = &vb; + vi.vertexAttributeDescriptionCount = 1; + vi.pVertexAttributeDescriptions = &via; VkPipelineViewportStateCreateInfo vp = {}; vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; @@ -125,8 +184,19 @@ namespace vk VkPipelineColorBlendAttachmentState att = {}; if (renderpass_config.write_color) + { att.colorWriteMask = 0xf; + if (renderpass_config.enable_blend) + { + att.blendEnable = VK_TRUE; + att.alphaBlendOp = VK_BLEND_OP_ADD; + att.colorBlendOp = VK_BLEND_OP_ADD; + att.dstAlphaBlendFactor = att.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + att.srcAlphaBlendFactor = att.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + } + } + VkPipelineColorBlendStateCreateInfo cs = {}; cs.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; cs.attachmentCount = renderpass_config.color_attachments; @@ -158,17 +228,14 @@ namespace vk CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, nullptr, 1, &info, NULL, &pipeline)); - const std::vector unused; - std::vector fs_inputs; - fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_texture, {}, {}, 0, "fs0"}); - auto program = std::make_unique(*m_device, pipeline, unused, fs_inputs); + auto program = std::make_unique(*m_device, pipeline, get_vertex_inputs(), get_fragment_inputs()); auto result = program.get(); m_program_cache[render_pass] = std::move(program); return result; } - void load_program(vk::command_buffer cmd, VkRenderPass pass, vk::image_view *src) + void load_program(vk::command_buffer cmd, VkRenderPass pass, VkImageView src) { vk::glsl::program *program = nullptr; auto found = m_program_cache.find(pass); @@ -195,11 +262,18 @@ namespace vk VK_FALSE, 0.f, 1.f, 0.f, 0.f, VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); } - VkDescriptorImageInfo info = { m_sampler->value, src->value, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; + VkDescriptorImageInfo info = { m_sampler->value, src, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; program->bind_uniform(info, "fs0", m_descriptor_set); + program->bind_uniform({ m_ubo->value, first_vertex * 128, 128 }, 1, m_descriptor_set); + + update_uniforms(program); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, program->pipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &m_descriptor_set, 0, nullptr); + + VkBuffer buffers = m_vao->value; + VkDeviceSize offsets = 0; + vkCmdBindVertexBuffers(cmd, 0, 1, &buffers, &offsets); } void create(vk::render_device &dev) @@ -274,10 +348,13 @@ namespace vk return result; } - void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, vk::image_view* src, VkRenderPass render_pass, std::list>& framebuffer_resources) + virtual void emit_geometry(vk::command_buffer &cmd) { - vk::framebuffer *fbo = get_framebuffer(target, render_pass, framebuffer_resources); + vkCmdDraw(cmd, num_drawable_elements, 1, first_vertex, 0); + } + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::framebuffer* fbo, VkImageView src, VkRenderPass render_pass) + { load_program(cmd, render_pass, src); VkViewport vp{}; @@ -300,9 +377,20 @@ namespace vk rp_begin.renderArea.extent.height = h; vkCmdBeginRenderPass(cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); - vkCmdDraw(cmd, 4, 1, 0, 0); + emit_geometry(cmd); vkCmdEndRenderPass(cmd); } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, VkImageView src, VkRenderPass render_pass, std::list>& framebuffer_resources) + { + vk::framebuffer *fbo = get_framebuffer(target, render_pass, framebuffer_resources); + run(cmd, w, h, fbo, src, render_pass); + } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, vk::image_view* src, VkRenderPass render_pass, std::list>& framebuffer_resources) + { + run(cmd, w, h, target, src->value, render_pass, framebuffer_resources); + } }; struct depth_convert_pass : public overlay_pass @@ -344,4 +432,288 @@ namespace vk m_fragment_shader.id = 100003; } }; + + struct ui_overlay_renderer : public overlay_pass + { + f32 m_time = 0.f; + color4f m_scale_offset; + color4f m_color; + bool m_pulse_glow = false; + bool m_skip_texture_read = false; + bool m_clip_enabled; + areaf m_clip_region; + + std::vector> resources; + std::unordered_map> font_cache; + std::unordered_map> view_cache; + std::vector> temp_image_cache; + std::unordered_map> temp_view_cache; + + ui_overlay_renderer() + { + vs_src = + { + "#version 450\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "layout(location=0) in vec4 in_pos;\n" + "layout(std140, set=0, binding=1) uniform static_data{ vec4 regs[8]; };\n" + "layout(location=0) out vec2 tc0;\n" + "layout(location=1) out vec4 color;\n" + "layout(location=2) out vec4 parameters;\n" + "layout(location=3) out vec4 clip_rect;\n" + "\n" + "void main()\n" + "{\n" + " tc0.xy = in_pos.zw;\n" + " color = regs[1];\n" + " parameters = regs[2];\n" + " clip_rect = regs[3] * regs[0].zwzw;\n" + " vec4 pos = vec4((in_pos.xy * regs[0].zw) / regs[0].xy, 0.5, 1.);\n" + " gl_Position = (pos + pos) - 1.;\n" + "}\n" + }; + + fs_src = + { + "#version 420\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "layout(set=0, binding=0) uniform sampler2D fs0;\n" + "layout(location=0) in vec2 tc0;\n" + "layout(location=1) in vec4 color;\n" + "layout(location=2) in vec4 parameters;\n" + "layout(location=3) in vec4 clip_rect;\n" + "layout(location=0) out vec4 ocol;\n" + "\n" + "void main()\n" + "{\n" + " if (parameters.w != 0)\n" + " {" + " if (gl_FragCoord.x < clip_rect.x || gl_FragCoord.x > clip_rect.z ||\n" + " gl_FragCoord.y < clip_rect.y || gl_FragCoord.y > clip_rect.w)\n" + " {\n" + " discard;\n" + " return;\n" + " }\n" + " }\n" + "\n" + " vec4 diff_color = color;\n" + " if (parameters.y != 0)\n" + " diff_color.a *= (sin(parameters.x) + 1.f) * 0.5f;\n" + "\n" + " if (parameters.z != 0)\n" + " ocol = texture(fs0, tc0) * diff_color;\n" + " else\n" + " ocol = diff_color;\n" + "}\n" + }; + + renderpass_config.color_attachments = 1; + renderpass_config.write_color = true; + renderpass_config.write_depth = false; + renderpass_config.enable_blend = true; + + m_vertex_shader.id = 100004; + m_fragment_shader.id = 100005; + } + + vk::image_view* upload_simple_texture(vk::render_device &dev, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, + vk::vk_data_heap& upload_heap, u64 key, int w, int h, bool font, bool temp, void *pixel_src) + { + const VkFormat format = (font) ? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM; + const u32 pitch = (font) ? w : w * 4; + const u32 data_size = pitch * h; + const auto offset = upload_heap.alloc<512>(data_size); + const auto addr = upload_heap.map(offset, data_size); + + const VkComponentMapping bgra = { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A }; + const VkComponentMapping rrrr = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R }; + const VkComponentMapping mapping = (font) ? rrrr : bgra; + + const VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + auto tex = std::make_unique(dev, memory_types.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_TYPE_2D, format, w, h, 1, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + 0); + + if (pixel_src && data_size) + std::memcpy(addr, pixel_src, data_size); + else if (data_size) + std::memset(addr, 0, data_size); + + upload_heap.unmap(); + + VkBufferImageCopy region; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.bufferOffset = offset; + region.bufferRowLength = w; + region.bufferImageHeight = h; + region.imageOffset = {}; + region.imageExtent = { (u32)w, (u32)h, 1u}; + + change_image_layout(cmd, tex.get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, range); + vkCmdCopyBufferToImage(cmd, upload_heap.heap->value, tex->value, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + change_image_layout(cmd, tex.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, range); + + auto view = std::make_unique(dev, tex.get(), range, mapping); + + auto result = view.get(); + + if (!temp || font) + view_cache[key] = std::move(view); + else + temp_view_cache[key] = std::move(view); + + if (font) + font_cache[key] = std::move(tex); + else if (!temp) + resources.push_back(std::move(tex)); + else + temp_image_cache.push_back(std::move(tex)); + + return result; + } + + void create(vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + auto& dev = cmd.get_command_pool().get_owner(); + overlay_pass::create(dev); + + rsx::overlays::resource_config configuration; + configuration.load_files(); + + u64 storage_key = 1; + for (const auto &res : configuration.texture_raw_data) + { + upload_simple_texture(dev, cmd, memory_types, upload_heap, storage_key++, res->w, res->h, false, false, res->data); + } + + configuration.free_resources(); + } + + void destroy() + { + temp_image_cache.clear(); + temp_view_cache.clear(); + + resources.clear(); + font_cache.clear(); + view_cache.clear(); + + overlay_pass::destroy(); + } + + void remove_temp_resources() + { + temp_image_cache.clear(); + temp_view_cache.clear(); + } + + vk::image_view* find_font(rsx::overlays::font *font, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + u64 key = (u64)font; + auto found = view_cache.find(key); + if (found != view_cache.end()) + return found->second.get(); + + //Create font file + return upload_simple_texture(cmd.get_command_pool().get_owner(), cmd, memory_types, upload_heap, key, font->width, font->height, true, false, font->glyph_data.data()); + } + + vk::image_view* find_temp_image(rsx::overlays::image_info *desc, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + u64 key = (u64)desc; + auto found = temp_view_cache.find(key); + if (found != temp_view_cache.end()) + return found->second.get(); + + return upload_simple_texture(cmd.get_command_pool().get_owner(), cmd, memory_types, upload_heap, key, desc->w, desc->h, false, true, desc->data); + } + + void update_uniforms(vk::glsl::program *program) override + { + auto dst = (f32*)m_ubo->map(first_vertex * 128, 128); + dst[0] = m_scale_offset.r; + dst[1] = m_scale_offset.g; + dst[2] = m_scale_offset.b; + dst[3] = m_scale_offset.a; + dst[4] = m_color.r; + dst[5] = m_color.g; + dst[6] = m_color.b; + dst[7] = m_color.a; + dst[8] = m_time; + dst[9] = m_pulse_glow? 1.f : 0.f; + dst[10] = m_skip_texture_read? 0.f : 1.f; + dst[11] = m_clip_enabled ? 1.f : 0.f; + dst[12] = m_clip_region.x1; + dst[13] = m_clip_region.y1; + dst[14] = m_clip_region.x2; + dst[15] = m_clip_region.y2; + m_ubo->unmap(); + } + + void emit_geometry(vk::command_buffer &cmd) + { + //Split into groups of 4 + auto tmp_first = first_vertex; + auto num_quads = num_drawable_elements / 4; + + for (u32 n = 0; n < num_quads; ++n) + { + vkCmdDraw(cmd, 4, 1, tmp_first, 0); + tmp_first += 4; + } + } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::framebuffer* target, VkRenderPass render_pass, vk::memory_type_mapping &memory_types, + vk::vk_data_heap &upload_heap, rsx::overlays::user_interface &ui) + { + m_scale_offset = color4f((f32)ui.virtual_width, (f32)ui.virtual_height, 1.f, 1.f); + m_time = (f32)(get_system_time() / 1000) * 0.005f; + + u32 vertex_data_offset = 0; + first_vertex = 0; + + for (auto &command : ui.get_compiled().draw_commands) + { + num_drawable_elements = (u32)command.second.size(); + const u32 value_count = num_drawable_elements * 4; + + upload_vertex_data((f32*)command.second.data(), vertex_data_offset, value_count); + + m_skip_texture_read = false; + m_color = command.first.color; + m_pulse_glow = command.first.pulse_glow; + m_clip_enabled = command.first.clip_region; + m_clip_region = command.first.clip_rect; + + auto src = vk::null_image_view(cmd); + switch (command.first.texture_ref) + { + case rsx::overlays::image_resource_id::game_icon: + case rsx::overlays::image_resource_id::backbuffer: + //TODO + case rsx::overlays::image_resource_id::none: + m_skip_texture_read = true; + break; + case rsx::overlays::image_resource_id::font_file: + src = find_font(command.first.font_ref, cmd, memory_types, upload_heap)->value; + break; + case rsx::overlays::image_resource_id::raw_image: + src = find_temp_image((rsx::overlays::image_info*)command.first.external_data_ref, cmd, memory_types, upload_heap)->value; + break; + default: + src = view_cache[command.first.texture_ref]->value; + break; + } + + overlay_pass::run(cmd, w, h, target, src, render_pass); + + vertex_data_offset += value_count; + first_vertex += num_drawable_elements; + } + + ui.update(); + } + }; } diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index bc5e793ee4..93d90fb2cb 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -149,7 +149,7 @@ namespace vk void copy_mipmaped_image_using_buffer(VkCommandBuffer cmd, VkImage dst_image, const std::vector& subresource_layout, int format, bool is_swizzled, u16 mipmap_count, - VkImageAspectFlags flags, vk::vk_data_heap &upload_heap, vk::buffer* upload_buffer) + VkImageAspectFlags flags, vk::vk_data_heap &upload_heap) { u32 mipmap_level = 0; u32 block_in_pixel = get_format_block_size_in_texel(format); @@ -160,10 +160,10 @@ namespace vk u32 image_linear_size = row_pitch * layout.height_in_block * layout.depth; size_t offset_in_buffer = upload_heap.alloc<512>(image_linear_size); - void *mapped_buffer = upload_buffer->map(offset_in_buffer, image_linear_size); + void *mapped_buffer = upload_heap.map(offset_in_buffer, image_linear_size); gsl::span mapped{ (gsl::byte*)mapped_buffer, ::narrow(image_linear_size) }; upload_texture_subresource(mapped, layout, format, is_swizzled, 256); - upload_buffer->unmap(); + upload_heap.unmap(); VkBufferImageCopy copy_info = {}; copy_info.bufferOffset = offset_in_buffer; @@ -176,7 +176,7 @@ namespace vk copy_info.imageSubresource.mipLevel = mipmap_level % mipmap_count; copy_info.bufferRowLength = block_in_pixel * row_pitch / block_size_in_bytes; - vkCmdCopyBufferToImage(cmd, upload_buffer->value, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_info); + vkCmdCopyBufferToImage(cmd, upload_heap.heap->value, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_info); mipmap_level++; } } diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.h b/rpcs3/Emu/RSX/VK/VKTextureCache.h index a2cd9b75eb..0cddd96a4a 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.h +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.h @@ -425,7 +425,6 @@ namespace vk vk::gpu_formats_support m_formats_support; VkQueue m_submit_queue; vk_data_heap* m_texture_upload_heap; - vk::buffer* m_texture_upload_buffer; //Stuff that has been dereferenced goes into these std::list m_discardable_storage; @@ -787,7 +786,7 @@ namespace vk } vk::copy_mipmaped_image_using_buffer(cmd, image->value, subresource_layout, gcm_format, input_swizzled, mipmaps, subres_range.aspectMask, - *m_texture_upload_heap, m_texture_upload_buffer); + *m_texture_upload_heap); vk::leave_uninterruptible(); @@ -848,14 +847,13 @@ namespace vk public: void initialize(vk::render_device& device, vk::memory_type_mapping& memory_types, vk::gpu_formats_support& formats_support, - VkQueue submit_queue, vk::vk_data_heap& upload_heap, vk::buffer* upload_buffer) + VkQueue submit_queue, vk::vk_data_heap& upload_heap) { m_memory_types = memory_types; m_formats_support = formats_support; m_device = &device; m_submit_queue = submit_queue; m_texture_upload_heap = &upload_heap; - m_texture_upload_buffer = upload_buffer; } void destroy() override diff --git a/rpcs3/Emu/RSX/overlay_controls.h b/rpcs3/Emu/RSX/overlay_controls.h new file mode 100644 index 0000000000..67482c120a --- /dev/null +++ b/rpcs3/Emu/RSX/overlay_controls.h @@ -0,0 +1,1456 @@ +#pragma once +#include "Utilities/types.h" +#include "Utilities/geometry.h" + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +// STB_IMAGE_IMPLEMENTATION and STB_TRUETYPE_IMPLEMENTATION defined externally +#include +#include + +// Definitions for common UI controls and their routines +namespace rsx +{ + namespace overlays + { + enum image_resource_id : u8 + { + //NOTE: 1 - 252 are user defined + none = 0, //No image + raw_image = 252, //Raw image data passed via image_info struct + font_file = 253, //Font file + game_icon = 254, //Use game icon + backbuffer = 255 //Use current backbuffer contents + }; + + struct vertex + { + float values[4]; + + vertex() {} + + vertex(float x, float y) + { + vec2(x, y); + } + + vertex(float x, float y, float z) + { + vec3(x, y, z); + } + + vertex(float x, float y, float z, float w) + { + vec4(x, y, z, w); + } + + float& operator[](int index) + { + return values[index]; + } + + void vec2(float x, float y) + { + values[0] = x; + values[1] = y; + values[2] = 0.f; + values[3] = 1.f; + } + + void vec3(float x, float y, float z) + { + values[0] = x; + values[1] = y; + values[2] = z; + values[3] = 1.f; + } + + void vec4(float x, float y, float z, float w) + { + values[0] = x; + values[1] = y; + values[2] = z; + values[3] = w; + } + + void operator += (const vertex& other) + { + values[0] += other.values[0]; + values[1] += other.values[1]; + values[2] += other.values[2]; + values[3] += other.values[3]; + } + + void operator -= (const vertex& other) + { + values[0] -= other.values[0]; + values[1] -= other.values[1]; + values[2] -= other.values[2]; + values[3] -= other.values[3]; + } + }; + + struct font + { + const u32 width = 1024; + const u32 height = 1024; + const u32 oversample = 2; + const u32 char_count = 256; //16x16 grid at max 48pt + + f32 size_pt = 12.f; + f32 size_px = 16.f; //Default font 12pt size + f32 em_size = 0.f; + std::string font_name; + std::vector pack_info; + std::vector glyph_data; + bool initialized = false; + + font(const char *ttf_name, f32 size) + { + //Init glyph + std::vector bytes; +#ifdef _WIN32 + std::string font_dir = "C:/Windows/Fonts/"; + std::string fallback_font = "C:/Windows/Fonts/Arial.ttf"; +#else + char *home = getenv("HOME"); + if (home == nullptr) + home = getpwuid(getuid())->pw_dir; + + std::string font_dir = home; + if (home[font_dir.length() - 1] == '/') + font_dir += ".fonts/"; + else + font_dir += "/.fonts/"; + + std::string fallback_font = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"; +#endif + std::string requested_file = font_dir + ttf_name + ".ttf"; + std::string file_path = requested_file; + + if (!fs::is_file(requested_file)) + { + if (fs::is_file(fallback_font)) + { + //TODO: Multiple fallbacks + file_path = fallback_font; + } + else + { + LOG_ERROR(RSX, "Failed to initialize font '%s.ttf'", ttf_name); + return; + } + } + + fs::file f(file_path); + f.read(bytes, f.size()); + + glyph_data.resize(width * height); + pack_info.resize(256); + + stbtt_pack_context context; + if (!stbtt_PackBegin(&context, glyph_data.data(), width, height, 0, 1, nullptr)) + { + LOG_ERROR(RSX, "Font packing failed"); + return; + } + + stbtt_PackSetOversampling(&context, oversample, oversample); + + //Convert pt to px + size_px = ceilf((f32)size * 96.f / 72.f); + size_pt = size; + + if (!stbtt_PackFontRange(&context, bytes.data(), 0, size_px, 0, 256, pack_info.data())) + { + LOG_ERROR(RSX, "Font packing failed"); + stbtt_PackEnd(&context); + return; + } + + stbtt_PackEnd(&context); + + font_name = ttf_name; + initialized = true; + + f32 unused; + get_char('m', em_size, unused); + } + + stbtt_aligned_quad get_char(char c, f32 &x_advance, f32 &y_advance) + { + if (!initialized) + return{}; + + stbtt_aligned_quad quad; + stbtt_GetPackedQuad(pack_info.data(), width, height, c, &x_advance, &y_advance, &quad, true); + return quad; + } + + std::vector render_text(const char *text, u16 text_limit = UINT16_MAX, bool wrap = false) + { + if (!initialized) + { + return{}; + } + + std::vector result; + + int i = 0; + f32 x_advance = 0.f, y_advance = 0.f; + bool skip_whitespace = false; + + while (true) + { + if (char c = text[i++]) + { + if ((u32)c >= char_count) + { + //Unsupported glyph, render null for now + c = ' '; + } + + switch (c) + { + case '\n': + { + y_advance += size_px + 2.f; + x_advance = 0.f; + continue; + } + case '\r': + { + x_advance = 0.f; + continue; + } + default: + { + stbtt_aligned_quad quad; + if (skip_whitespace && text[i - 1] == ' ') + { + quad = {}; + } + else + { + quad = get_char(c, x_advance, y_advance); + skip_whitespace = false; + } + + if (quad.x1 > text_limit) + { + bool wrapped = false; + bool non_whitespace_break = false; + + if (wrap) + { + //scan previous chars + for (int j = i - 1, nb_chars = 0; j > 0; j--, nb_chars++) + { + if (text[j] == '\n') + break; + + if (text[j] == ' ') + { + non_whitespace_break = true; + continue; + } + + if (non_whitespace_break) + { + if (nb_chars > 1) + { + nb_chars--; + + auto first_affected = result.size() - (nb_chars * 4); + f32 base_x = result[first_affected].values[0]; + + for (size_t n = first_affected; n < result.size(); ++n) + { + auto char_index = n / 4; + if (text[char_index] == ' ') + { + //Skip character + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n].vec2(0.f, 0.f); + continue; + } + + result[n].values[0] -= base_x; + result[n].values[1] += size_px + 2.f; + } + + x_advance = result.back().values[0]; + } + else + { + x_advance = 0.f; + } + + wrapped = true; + y_advance += size_px + 2.f; + + if (text[i - 1] == ' ') + { + quad = {}; + skip_whitespace = true; + } + else + { + quad = get_char(c, x_advance, y_advance); + } + + break; + } + } + } + + if (!wrapped) + { + //TODO: Ellipsize + break; + } + } + + result.push_back({ quad.x0, quad.y0, quad.s0, quad.t0 }); + result.push_back({ quad.x1, quad.y0, quad.s1, quad.t0 }); + result.push_back({ quad.x0, quad.y1, quad.s0, quad.t1 }); + result.push_back({ quad.x1, quad.y1, quad.s1, quad.t1 }); + break; + } + } //switch + } + else + { + break; + } + } + + return result; + } + + }; + + //TODO: Singletons are cancer + class fontmgr + { + private: + std::vector> fonts; + static fontmgr *m_instance; + + font* find(const char *name, int size) + { + for (auto &f : fonts) + { + if (f->font_name == name && + f->size_pt == size) + return f.get(); + } + + fonts.push_back(std::make_unique(name, (f32)size)); + return fonts.back().get(); + } + + public: + + fontmgr() {} + ~fontmgr() + { + if (m_instance) + { + delete m_instance; + m_instance = nullptr; + } + } + + static font* get(const char *name, int size) + { + if (m_instance == nullptr) + m_instance = new fontmgr; + + return m_instance->find(name, size); + } + }; + + struct image_info + { + int w = 0, h = 0; + int bpp = 0; + u8* data = nullptr; + + image_info(image_info&) = delete; + + image_info(const char* filename) + { + if (!fs::is_file(filename)) + { + LOG_ERROR(RSX, "Image resource file `%s' not found", filename); + return; + } + + std::vector bytes; + fs::file f(filename); + f.read(bytes, f.size()); + data = stbi_load_from_memory(bytes.data(), (s32)f.size(), &w, &h, &bpp, STBI_rgb_alpha); + } + + image_info(const std::vector& bytes) + { + data = stbi_load_from_memory(bytes.data(), (s32)bytes.size(), &w, &h, &bpp, STBI_rgb_alpha); + } + + ~image_info() + { + stbi_image_free(data); + data = nullptr; + w = h = bpp = 0; + } + }; + + struct resource_config + { + enum standard_image_resource : u8 + { + fade_top = 1, + fade_bottom, + cross, + circle, + triangle, + square, + save, + new_entry + }; + + //Define resources + std::vector texture_resource_files; + std::vector> texture_raw_data; + + resource_config() + { + texture_resource_files.push_back("fade_top.png"); + texture_resource_files.push_back("fade_bottom.png"); + texture_resource_files.push_back("cross.png"); + texture_resource_files.push_back("circle.png"); + texture_resource_files.push_back("triangle.png"); + texture_resource_files.push_back("square.png"); + texture_resource_files.push_back("save.png"); + texture_resource_files.push_back("new.png"); + } + + void load_files() + { + for (const auto &res : texture_resource_files) + { + //First check the global config dir + auto info = std::make_unique((fs::get_config_dir() + "Icons/ui/" + res).c_str()); + + if (info->data == nullptr) + { + //Resource was not found in config dir, try and grab from relative path (linux) + info = std::make_unique(("Icons/ui/" + res).c_str()); + + if (info->data != nullptr) + { + //Install the image to config dir + auto dst_dir = fs::get_config_dir() + "Icons/ui/"; + auto src = "Icons/ui/" + res; + auto dst = dst_dir + res; + + if (!fs::is_dir(dst_dir)) + { + auto root_folder = fs::get_config_dir() + "Icons/"; + if (!fs::is_dir(root_folder)) + fs::create_dir(root_folder); + + fs::create_dir(dst_dir); + } + + fs::copy_file(src, dst, true); + } + } + + texture_raw_data.push_back(std::move(info)); + } + } + + void free_resources() + { + texture_raw_data.clear(); + } + }; + + struct compiled_resource + { + struct command_config + { + color4f color = { 1.f, 1.f, 1.f, 1.f }; + bool pulse_glow = false; + + areaf clip_rect = {}; + bool clip_region = false; + + u8 texture_ref = image_resource_id::none; + font *font_ref = nullptr; + void *external_data_ref = nullptr; + + command_config() {} + + command_config(u8 ref) + { + texture_ref = ref; + font_ref = nullptr; + } + + command_config(font *ref) + { + texture_ref = image_resource_id::font_file; + font_ref = ref; + } + }; + + std::vector>> draw_commands; + + void add(const compiled_resource& other) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + } + + void add(const compiled_resource& other, f32 x_offset, f32 y_offset) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + + for (size_t n = old_size; n < draw_commands.size(); ++n) + { + for (auto &v : draw_commands[n].second) + { + v += vertex(x_offset, y_offset, 0.f, 0.f); + } + } + } + + void add(const compiled_resource& other, f32 x_offset, f32 y_offset, const areaf& clip_rect) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + + for (size_t n = old_size; n < draw_commands.size(); ++n) + { + for (auto &v : draw_commands[n].second) + { + v += vertex(x_offset, y_offset, 0.f, 0.f); + } + + draw_commands[n].first.clip_rect = clip_rect; + draw_commands[n].first.clip_region = true; + } + } + }; + + struct overlay_element + { + enum text_align + { + left = 0, + center + }; + + u16 x = 0; + u16 y = 0; + u16 w = 0; + u16 h = 0; + + std::string text; + font* font_ref = nullptr; + text_align alignment = left; + bool wrap_text = false; + bool clip_text = true; + + color4f back_color = { 0.f, 0.f, 0.f, 1.f }; + color4f fore_color = { 1.f, 1.f, 1.f, 1.f }; + bool pulse_effect_enabled = false; + + compiled_resource compiled_resources; + bool is_compiled = false; + + f32 padding_left = 0.f; + f32 padding_right = 0.f; + f32 padding_top = 0.f; + f32 padding_bottom = 0.f; + + overlay_element() {} + overlay_element(u16 _w, u16 _h) : w(_w), h(_h) {} + + virtual void refresh() + { + //Just invalidate for draw when get_compiled() is called + is_compiled = false; + } + + virtual void translate(s16 _x, s16 _y) + { + x = (u16)(x + _x); + y = (u16)(y + _y); + + is_compiled = false; + } + + virtual void scale(f32 _x, f32 _y, bool origin_scaling) + { + if (origin_scaling) + { + x = (u16)(_x * x); + y = (u16)(_y * y); + } + + w = (u16)(_x * w); + h = (u16)(_y * h); + + is_compiled = false; + } + + virtual void set_pos(u16 _x, u16 _y) + { + x = _x; + y = _y; + + is_compiled = false; + } + + virtual void set_size(u16 _w, u16 _h) + { + w = _w; + h = _h; + + is_compiled = false; + } + + virtual void set_padding(f32 left, f32 right, f32 top, f32 bottom) + { + padding_left = left; + padding_right = right; + padding_top = top; + padding_bottom = bottom; + + is_compiled = false; + } + + virtual void set_padding(f32 padding) + { + padding_left = padding_right = padding_top = padding_bottom = padding; + is_compiled = false; + } + + virtual void set_text(const std::string& text) + { + this->text = text; + is_compiled = false; + } + + virtual void set_text(const char* text) + { + this->text = text; + is_compiled = false; + } + + virtual void set_font(const char* font_name, u16 font_size) + { + font_ref = fontmgr::get(font_name, font_size); + is_compiled = false; + } + + virtual void align_text(text_align align) + { + alignment = align; + is_compiled = false; + } + + virtual void set_wrap_text(bool state) + { + wrap_text = state; + is_compiled = false; + } + + virtual std::vector render_text(const char *string, f32 x, f32 y) + { + auto renderer = font_ref; + if (!renderer) renderer = fontmgr::get("Arial", 12); + + f32 text_extents_w = 0.f; + u16 clip_width = clip_text ? w : UINT16_MAX; + std::vector result = renderer->render_text(string, clip_width, wrap_text); + + if (result.size() > 0) + { + for (auto &v : result) + { + //Check for real text region extent + //TODO: Ellipsis + text_extents_w = std::max(v.values[0], text_extents_w); + + //Apply transform. + //(0, 0) has text sitting 50% off the top left corner (text is outside the rect) hence the offset by text height / 2 + v.values[0] += x + padding_left; + v.values[1] += y + padding_top + (f32)renderer->size_px * 0.5f; + } + + if (alignment == center) + { + //Scan for lines and measure them + //Reposition them to the center + std::vector> lines; + u32 line_begin = 0; + u32 ctr = 0; + + for (auto c : text) + { + switch (c) + { + case '\r': + continue; + case '\n': + lines.push_back({ line_begin, ctr }); + line_begin = ctr; + continue; + default: + ctr += 4; + } + } + + lines.push_back({ line_begin, ctr }); + const auto max_region_w = std::max(text_extents_w, w); + + for (auto p : lines) + { + if (p.first >= p.second) + continue; + + const f32 line_length = result[p.second - 1].values[0] - result[p.first].values[0]; + const bool wrapped = fabs(result[p.second - 1].values[1] - result[p.first + 3].values[1]) >= (renderer->size_px * 0.5f); + + if (wrapped) + continue; + + if (line_length < max_region_w) + { + f32 offset = (max_region_w - line_length) * 0.5f; + for (auto n = p.first; n < p.second; ++n) + { + result[n].values[0] += offset; + } + } + } + } + } + + return result; + } + + virtual compiled_resource& get_compiled() + { + if (!is_compiled) + { + compiled_resources = {}; + compiled_resources.draw_commands.push_back({}); + + auto &config = compiled_resources.draw_commands.front().first; + config.color = back_color; + config.pulse_glow = pulse_effect_enabled; + + auto& verts = compiled_resources.draw_commands.front().second; + verts.resize(4); + verts[0].vec4(x + padding_left, y + padding_bottom, 0.f, 0.f); + verts[1].vec4(x + w - padding_right, y + padding_bottom, 1.f, 0.f); + verts[2].vec4(x + padding_left, y + h - padding_top, 0.f, 1.f); + verts[3].vec4(x + w - padding_right, y + h - padding_top, 1.f, 1.f); + + if (!text.empty()) + { + compiled_resources.draw_commands.push_back({}); + compiled_resources.draw_commands.back().first = font_ref? font_ref : fontmgr::get("Arial", 12); + compiled_resources.draw_commands.back().first.color = fore_color; + compiled_resources.draw_commands.back().second = render_text(text.c_str(), (f32)x, (f32)y); + + if (compiled_resources.draw_commands.back().second.size() == 0) + compiled_resources.draw_commands.pop_back(); + } + + is_compiled = true; + } + + return compiled_resources; + } + + void measure_text(u16& width, u16& height, bool ignore_word_wrap = false) const + { + if (text.empty()) + { + width = height = 0; + return; + } + + auto renderer = font_ref; + if (!renderer) renderer = fontmgr::get("Arial", 12); + + f32 text_width = 0.f; + f32 unused = 0.f; + f32 max_w = 0.f; + f32 last_word = 0.f; + height = (u16)renderer->size_px; + + for (auto c : text) + { + if (c == '\n') + { + height += (u16)renderer->size_px + 2; + max_w = std::max(max_w, text_width); + text_width = 0.f; + last_word = 0.f; + continue; + } + + if (c == ' ') + { + last_word = text_width; + } + + renderer->get_char(c, text_width, unused); + + if (!ignore_word_wrap && wrap_text && text_width >= w) + { + if ((text_width - last_word) < w) + { + max_w = std::max(max_w, last_word); + text_width -= (last_word + renderer->em_size); + height += (u16)renderer->size_px + 2; + } + } + } + + max_w = std::max(max_w, text_width); + width = (u16)ceilf(max_w); + } + + }; + + struct animation_base + { + float duration = 0.f; + float t = 0.f; + overlay_element *ref = nullptr; + + virtual void update(float /*elapsed*/) {} + void reset() { t = 0.f; } + }; + + struct layout_container : public overlay_element + { + std::vector> m_items; + u16 advance_pos = 0; + u16 pack_padding = 0; + u16 scroll_offset_value = 0; + bool auto_resize = true; + + virtual overlay_element* add_element(std::unique_ptr&, int = -1) = 0; + + layout_container() + { + //Transparent by default + back_color.a = 0.f; + } + + void translate(s16 _x, s16 _y) override + { + overlay_element::translate(_x, _y); + + for (auto &itm : m_items) + itm->translate(_x, _y); + } + + void set_pos(u16 _x, u16 _y) override + { + s16 dx = (s16)(_x - x); + s16 dy = (s16)(_y - y); + translate(dx, dy); + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + + for (auto &itm : m_items) + result.add(itm->get_compiled()); + + compiled_resources = result; + } + + return compiled_resources; + } + + virtual u16 get_scroll_offset_px() = 0; + }; + + struct vertical_layout : public layout_container + { + overlay_element* add_element(std::unique_ptr& item, int offset = -1) + { + if (auto_resize) + { + item->set_pos(item->x + x, h + pack_padding + y); + h += item->h + pack_padding; + w = std::max(w, item->w); + } + else + { + item->set_pos(item->x + x, advance_pos + pack_padding + y); + advance_pos += item->h + pack_padding; + } + + if (offset < 0) + { + m_items.push_back(std::move(item)); + return m_items.back().get(); + } + else + { + auto result = item.get(); + m_items.insert(m_items.begin() + offset, std::move(item)); + return result; + } + } + + compiled_resource& get_compiled() override + { + if (scroll_offset_value == 0 && auto_resize) + return layout_container::get_compiled(); + + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + const f32 global_y_offset = (f32)-scroll_offset_value; + + for (auto &item : m_items) + { + const s32 item_y_limit = (s32)(item->y + item->h) - scroll_offset_value - y; + const s32 item_y_base = (s32)item->y - scroll_offset_value - y; + + if (item_y_limit < 0 || item_y_base > h) + { + //Out of bounds + continue; + } + else if (item_y_limit > h || item_y_base < 0) + { + //Partial render + areaf clip_rect = { (f32)x, (f32)y, (f32)(x + w), (f32)(y + h) }; + result.add(item->get_compiled(), 0.f, global_y_offset, clip_rect); + } + else + { + //Normal + result.add(item->get_compiled(), 0.f, global_y_offset); + } + } + + compiled_resources = result; + } + + return compiled_resources; + } + + u16 get_scroll_offset_px() override + { + return scroll_offset_value; + } + }; + + struct horizontal_layout : public layout_container + { + overlay_element* add_element(std::unique_ptr& item, int offset = -1) + { + if (auto_resize) + { + item->set_pos(w + pack_padding + x, item->y + y); + w += item->w + pack_padding; + h = std::max(h, item->h); + } + else + { + item->set_pos(advance_pos + pack_padding + x, item->y + y); + advance_pos += item->w + pack_padding; + } + + if (offset < 0) + { + m_items.push_back(std::move(item)); + return m_items.back().get(); + } + else + { + auto result = item.get(); + m_items.insert(m_items.begin() + offset, std::move(item)); + return result; + } + } + + compiled_resource& get_compiled() override + { + if (scroll_offset_value == 0 && auto_resize) + return layout_container::get_compiled(); + + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + const f32 global_x_offset = (f32)-scroll_offset_value; + + for (auto &item : m_items) + { + const s32 item_x_limit = (s32)(item->x + item->w) - scroll_offset_value - w; + const s32 item_x_base = (s32)item->x - scroll_offset_value - w; + + if (item_x_limit < 0 || item_x_base > h) + { + //Out of bounds + continue; + } + else if (item_x_limit > h || item_x_base < 0) + { + //Partial render + areaf clip_rect = { (f32)x, (f32)y, (f32)(x + w), (f32)(y + h) }; + result.add(item->get_compiled(), global_x_offset, 0.f, clip_rect); + } + else + { + //Normal + result.add(item->get_compiled(), global_x_offset, 0.f); + } + } + + compiled_resources = result; + } + + return compiled_resources; + } + + u16 get_scroll_offset_px() override + { + return scroll_offset_value; + } + }; + + //Controls + struct spacer : public overlay_element + { + using overlay_element::overlay_element; + compiled_resource& get_compiled() override + { + //No draw + return compiled_resources; + } + }; + + struct image_view : public overlay_element + { + private: + u8 image_resource_ref = image_resource_id::none; + void *external_ref = nullptr; + + public: + using overlay_element::overlay_element; + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto &result = overlay_element::get_compiled(); + result.draw_commands.front().first = image_resource_ref; + result.draw_commands.front().first.color = fore_color; + result.draw_commands.front().first.external_data_ref = external_ref; + } + + return compiled_resources; + } + + void set_image_resource(u8 resource_id) + { + image_resource_ref = resource_id; + external_ref = nullptr; + } + + void set_raw_image(image_info *raw_image) + { + image_resource_ref = image_resource_id::raw_image; + external_ref = raw_image; + } + }; + + struct image_button : public image_view + { + u16 text_offset = 0; + + image_button() + { + //Do not clip text to region extents + //TODO: Define custom clipping region or use two controls to emulate + clip_text = false; + } + + image_button(u16 _w, u16 _h) + { + clip_text = false; + set_size(_w, _h); + } + + void set_size(u16 w, u16 h) override + { + image_view::set_size(h, h); + text_offset = (h / 2) + 10; //By default text is at the horizontal center + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto& compiled = image_view::get_compiled(); + for (auto &cmd : compiled.draw_commands) + { + if (cmd.first.texture_ref == image_resource_id::font_file) + { + //Text, translate geometry to the right + const f32 text_height = font_ref ? font_ref->size_px : 16.f; + const f32 offset_y = (h > text_height) ? (f32)(h - text_height) : ((f32)h - text_height); + + for (auto &v : cmd.second) + { + v.values[0] += text_offset + 15.f; + v.values[1] += offset_y + 5.f; + } + } + } + } + + return compiled_resources; + } + }; + + struct label : public overlay_element + { + label() {} + + label(const char *text) + { + this->text = text; + } + + void auto_resize(bool grow_only = false, u16 limit_w = UINT16_MAX, u16 limit_h = UINT16_MAX) + { + u16 new_width, new_height; + measure_text(new_width, new_height, true); + + if (new_width > limit_w && wrap_text) + measure_text(new_width, new_height, false); + + if (grow_only) + { + new_width = std::max(w, new_width); + new_height = std::max(h, new_height); + } + + w = std::min(new_width, limit_w); + h = std::min(new_height, limit_h); + } + }; + + struct progress_bar : public overlay_element + { + private: + overlay_element indicator; + label text_view; + + f32 m_limit = 100.f; + f32 m_value = 0.f; + + public: + using overlay_element::overlay_element; + + void inc(f32 value) + { + set_value(m_value + value); + } + + void dec(f32 value) + { + set_value(m_value - value); + } + + void set_limit(f32 limit) + { + m_limit = limit; + is_compiled = false; + } + + void set_value(f32 value) + { + if (value < 0.f) + m_value = 0.f; + else if (value > m_limit) + m_value = m_limit; + else + m_value = value; + + f32 indicator_width = (w * m_value) / m_limit; + indicator.set_size((u16)indicator_width, h); + is_compiled = false; + } + + void set_pos(u16 _x, u16 _y) override + { + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_h += 5; + + overlay_element::set_pos(_x, _y + text_h); + indicator.set_pos(_x, _y + text_h); + text_view.set_pos(_x, _y); + } + + void set_size(u16 _w, u16 _h) override + { + overlay_element::set_size(_w, _h); + text_view.set_size(_w, text_view.h); + set_value(m_value); + } + + void translate(s16 dx, s16 dy) override + { + set_pos(x + dx, y + dy); + } + + void set_text(const char* str) override + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + void set_text(const std::string& str) override + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto& compiled = overlay_element::get_compiled(); + compiled.add(text_view.get_compiled()); + + indicator.back_color = fore_color; + indicator.refresh(); + compiled.add(indicator.get_compiled()); + } + + return compiled_resources; + } + }; + + struct list_view : public vertical_layout + { + private: + std::unique_ptr m_scroll_indicator_top; + std::unique_ptr m_scroll_indicator_bottom; + std::unique_ptr m_cancel_btn; + std::unique_ptr m_accept_btn; + std::unique_ptr m_highlight_box; + + u16 m_elements_height = 0; + s16 m_selected_entry = -1; + u16 m_elements_count = 0; + + public: + list_view(u16 width, u16 height) + { + w = width; + h = height; + + m_scroll_indicator_top = std::make_unique(width, 5); + m_scroll_indicator_bottom = std::make_unique(width, 5); + m_accept_btn = std::make_unique(120, 20); + m_cancel_btn = std::make_unique(120, 20); + m_highlight_box = std::make_unique(width, 0); + + m_scroll_indicator_top->set_size(width, 40); + m_scroll_indicator_bottom->set_size(width, 40); + m_accept_btn->set_size(120, 30); + m_cancel_btn->set_size(120, 30); + + m_scroll_indicator_top->set_image_resource(resource_config::standard_image_resource::fade_top); + m_scroll_indicator_bottom->set_image_resource(resource_config::standard_image_resource::fade_bottom); + m_accept_btn->set_image_resource(resource_config::standard_image_resource::cross); + m_cancel_btn->set_image_resource(resource_config::standard_image_resource::circle); + + m_scroll_indicator_bottom->set_pos(0, height - 40); + m_accept_btn->set_pos(30, height + 20); + m_cancel_btn->set_pos(180, height + 20); + + m_accept_btn->text = "Select"; + m_cancel_btn->text = "Cancel"; + + auto fnt = fontmgr::get("Arial", 16); + m_accept_btn->font_ref = fnt; + m_cancel_btn->font_ref = fnt; + + auto_resize = false; + back_color = { 0.15f, 0.15f, 0.15f, 0.8f }; + + m_highlight_box->back_color = { .5f, .5f, .8f, 0.2f }; + m_highlight_box->pulse_effect_enabled = true; + m_scroll_indicator_top->fore_color.a = 0.f; + m_scroll_indicator_bottom->fore_color.a = 0.f; + } + + void update_selection() + { + auto current_element = m_items[m_selected_entry * 2].get(); + + //Calculate bounds + auto min_y = current_element->y - y; + auto max_y = current_element->y + current_element->h + pack_padding + 2 - y; + + if (min_y < scroll_offset_value) + { + scroll_offset_value = min_y; + } + else if (max_y > (h + scroll_offset_value)) + { + scroll_offset_value = max_y - h - 2; + } + + if ((scroll_offset_value + h + 2) >= m_elements_height) + m_scroll_indicator_bottom->fore_color.a = 0.f; + else + m_scroll_indicator_bottom->fore_color.a = 0.5f; + + if (scroll_offset_value == 0) + m_scroll_indicator_top->fore_color.a = 0.f; + else + m_scroll_indicator_top->fore_color.a = 0.5f; + + m_highlight_box->set_pos(current_element->x, current_element->y); + m_highlight_box->h = current_element->h + pack_padding; + m_highlight_box->y -= scroll_offset_value; + + m_highlight_box->refresh(); + m_scroll_indicator_top->refresh(); + m_scroll_indicator_bottom->refresh(); + refresh(); + } + + void select_next() + { + if (m_selected_entry < (m_elements_count - 1)) + { + m_selected_entry++; + update_selection(); + } + } + + void select_previous() + { + if (m_selected_entry > 0) + { + m_selected_entry--; + update_selection(); + } + } + + void add_entry(std::unique_ptr& entry) + { + //Add entry view + add_element(entry); + m_elements_count++; + + //Add separator + auto separator = std::make_unique(); + separator->back_color = fore_color; + separator->w = w; + separator->h = 2; + add_element(separator); + + if (m_selected_entry < 0) + m_selected_entry = 0; + + m_elements_height = advance_pos; + update_selection(); + } + + int get_selected_index() + { + return m_selected_entry; + } + + std::string get_selected_item() + { + if (m_selected_entry < 0) + return{}; + + return m_items[m_selected_entry]->text; + } + + void translate(s16 _x, s16 _y) override + { + layout_container::translate(_x, _y); + m_scroll_indicator_top->translate(_x, _y); + m_scroll_indicator_bottom->translate(_x, _y); + m_accept_btn->translate(_x, _y); + m_cancel_btn->translate(_x, _y); + } + + compiled_resource& get_compiled() + { + if (!is_compiled) + { + auto compiled = vertical_layout::get_compiled(); + compiled.add(m_highlight_box->get_compiled()); + compiled.add(m_scroll_indicator_top->get_compiled()); + compiled.add(m_scroll_indicator_bottom->get_compiled()); + compiled.add(m_accept_btn->get_compiled()); + compiled.add(m_cancel_btn->get_compiled()); + + compiled_resources = compiled; + } + + return compiled_resources; + } + }; + } +} diff --git a/rpcs3/Emu/RSX/overlays.cpp b/rpcs3/Emu/RSX/overlays.cpp new file mode 100644 index 0000000000..d95af226db --- /dev/null +++ b/rpcs3/Emu/RSX/overlays.cpp @@ -0,0 +1,31 @@ +#include "stdafx.h" +#include "overlays.h" +#include "GSRender.h" + +namespace rsx +{ + namespace overlays + { + //Singleton instance declaration + fontmgr* fontmgr::m_instance = nullptr; + + void user_interface::close() + { + //Force unload + exit = true; + if (auto rsxthr = fxm::get()) + rsxthr->shell_close_dialog(); + + if (on_close) + on_close(return_code); + } + + void user_interface::refresh() + { + if (auto rsxthr = fxm::get()) + { + rsxthr->native_ui_flip_request.store(true); + } + } + } +} diff --git a/rpcs3/Emu/RSX/overlays.h b/rpcs3/Emu/RSX/overlays.h new file mode 100644 index 0000000000..234ec613b4 --- /dev/null +++ b/rpcs3/Emu/RSX/overlays.h @@ -0,0 +1,762 @@ +#pragma once +#include "overlay_controls.h" + +#include "../../Utilities/Thread.h" +#include "../Io/PadHandler.h" +#include "Emu/Memory/vm.h" +#include "Emu/IdManager.h" +#include "pad_thread.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/Modules/cellSaveData.h" +#include "Emu/Cell/Modules/cellMsgDialog.h" +#include "Emu/Cell/Modules/sceNpTrophy.h" + +#include + +extern u64 get_system_time(); + +// Definition of user interface implementations +namespace rsx +{ + namespace overlays + { + struct user_interface + { + //Move this somewhere to avoid duplication + enum selection_code + { + new_save = -1, + canceled = -2, + error = -3 + }; + + enum pad_button : u8 + { + dpad_up = 0, + dpad_down, + dpad_left, + dpad_right, + triangle, + circle, + square, + cross + }; + + u16 virtual_width = 1280; + u16 virtual_height = 720; + + u64 input_timestamp = 0; + bool exit = false; + + s32 return_code = CELL_OK; + std::function on_close; + + virtual compiled_resource get_compiled() = 0; + + void close(); + void refresh(); + + virtual void update(){} + + virtual void on_button_pressed(pad_button button_press) + { + close(); + }; + + s32 run_input_loop() + { + const auto handler = fxm::get(); + if (!handler) + { + LOG_ERROR(RSX, "Pad handler expected but none initialized!"); + return selection_code::error; + } + + const PadInfo& rinfo = handler->GetInfo(); + if (rinfo.max_connect == 0 || !rinfo.now_connect) + return selection_code::error; + + std::array button_state; + button_state.fill(true); + + while (!exit) + { + if (Emu.IsStopped()) + return selection_code::canceled; + + if (Emu.IsPaused()) + { + std::this_thread::sleep_for(10ms); + continue; + } + + for (const auto &pad : handler->GetPads()) + { + for (auto &button : pad->m_buttons) + { + u8 button_id = 255; + if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1) + { + switch (button.m_outKeyCode) + { + case CELL_PAD_CTRL_LEFT: + button_id = pad_button::dpad_left; + break; + case CELL_PAD_CTRL_RIGHT: + button_id = pad_button::dpad_right; + break; + case CELL_PAD_CTRL_DOWN: + button_id = pad_button::dpad_down; + break; + case CELL_PAD_CTRL_UP: + button_id = pad_button::dpad_up; + break; + } + } + else if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2) + { + switch (button.m_outKeyCode) + { + case CELL_PAD_CTRL_TRIANGLE: + button_id = pad_button::triangle; + break; + case CELL_PAD_CTRL_CIRCLE: + button_id = pad_button::circle; + break; + case CELL_PAD_CTRL_SQUARE: + button_id = pad_button::square; + break; + case CELL_PAD_CTRL_CROSS: + button_id = pad_button::cross; + break; + } + } + + if (button_id < 255) + { + if (button.m_pressed != button_state[button_id]) + if (button.m_pressed) on_button_pressed(static_cast(button_id)); + + button_state[button_id] = button.m_pressed; + } + + if (button.m_flush) + { + button.m_pressed = false; + button.m_flush = false; + button.m_value = 0; + } + + if (exit) + return 0; + } + } + + refresh(); + } + + //Unreachable + return 0; + } + }; + + struct fps_display : user_interface + { + label m_display; + + fps_display() + { + m_display.w = 150; + m_display.h = 30; + m_display.set_font("Arial", 16); + m_display.set_pos(1100, 20); + } + + void update(std::string current_fps) + { + m_display.text = current_fps; + m_display.refresh(); + } + + compiled_resource get_compiled() override + { + return m_display.get_compiled(); + } + }; + + struct save_dialog : public user_interface + { + private: + struct save_dialog_entry : horizontal_layout + { + private: + std::unique_ptr icon_data; + + public: + save_dialog_entry(const char* text1, const char* text2, u8 resource_id, const std::vector& icon_buf) + { + std::unique_ptr image = std::make_unique(); + image->set_size(160, 110); + image->set_padding(36.f, 36.f, 11.f, 11.f); //Square image, 88x88 + + if (resource_id != image_resource_id::raw_image) + { + static_cast(image.get())->set_image_resource(resource_id); + } + else if (icon_buf.size()) + { + image->set_padding(0.f, 0.f, 11.f, 11.f); //Half sized icon, 320x176->160x88 + icon_data = std::make_unique(icon_buf); + static_cast(image.get())->set_raw_image(icon_data.get()); + } + else + { + //Fallback + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::save); + } + + std::unique_ptr text_stack = std::make_unique(); + std::unique_ptr padding = std::make_unique(); + std::unique_ptr header_text = std::make_unique