#include "pch.h" #include "platform_windows/windows_bootstrap_helpers.h" #include "platform_windows/windows_lifecycle_shell.h" #include "platform_windows/windows_platform_services.h" #include "platform_windows/windows_runtime_shell.h" #include "platform_windows/windows_stylus_input.h" #include "platform_windows/windows_window_shell.h" #include "app_runtime.h" #include "log.h" #include "legacy_gl_runtime_dispatch.h" #include "legacy_ui_gl_dispatch.h" #include "platform_api/network_tls_policy.h" #include "platform_api/platform_policy.h" #include "renderer_gl/opengl_capabilities.h" #include #include namespace pp::platform::windows { void set_async_render_context(HDC hdc, HGLRC hrc); void lock_async_render_context(); bool try_lock_async_render_context(); void unlock_async_render_context(); void swap_async_render_context(); RetainedState& retained_state(); } void destroy_window(); void async_lock(); void async_unlock(); void win32_async_swap(); void win32_update_fps(int frames); void win32_update_stylus(float dt); void win32_save_window_state(); bool win32_vr_start(); void win32_vr_stop(); HWND pp_windows_main_window_handle(); HWND pp_windows_main_window_handle() { return pp::platform::windows::retained_state().hWnd; } void destroy_window() { pp::platform::windows::enqueue_main_thread_task(std::packaged_task([hWnd = pp::platform::windows::retained_state().hWnd] { pp::platform::windows::request_window_close(hWnd); })); } void async_lock() { pp::platform::windows::lock_async_render_context(); } bool async_lock_try() { return pp::platform::windows::try_lock_async_render_context(); } void win32_async_swap() { pp::platform::windows::swap_async_render_context(); } void async_unlock() { pp::platform::windows::unlock_async_render_context(); } void win32_update_stylus(float dt) { pp::platform::windows::update_stylus_frame(dt); } void win32_update_fps(int frames) { auto& state = pp::platform::windows::retained_state(); pp::platform::windows::enqueue_main_thread_task(std::packaged_task([hWnd = state.hWnd, window_title = state.window_title, &vr = state.vr, frames] { pp::platform::windows::update_window_fps(hWnd, window_title, vr, frames); })); } bool win32_vr_start() { auto& state = pp::platform::windows::retained_state(); return pp::platform::windows::start_window_vr(state.vr, state.sandboxed); } void win32_vr_stop() { pp::platform::windows::stop_window_vr(pp::platform::windows::retained_state().vr); } void win32_save_window_state() { pp::platform::windows::save_window_preferences(pp::platform::windows::retained_state().hWnd); } namespace pp::platform::windows { namespace { struct RetainedWin32AsyncRenderContextState final { HDC hdc{}; HGLRC hrc{}; std::mutex gl_mutex; std::thread::id gl_thread{}; int gl_count = 0; }; [[nodiscard]] RetainedWin32AsyncRenderContextState& retained_win32_async_render_context_state() { static RetainedWin32AsyncRenderContextState state; return state; } } // namespace void set_async_render_context(HDC hdc, HGLRC hrc) { auto& state = retained_win32_async_render_context_state(); state.hdc = hdc; state.hrc = hrc; state.gl_thread = {}; state.gl_count = 0; } void lock_async_render_context() { auto& state = retained_win32_async_render_context_state(); if (state.gl_count == 0 || state.gl_thread != std::this_thread::get_id()) { state.gl_mutex.lock(); const bool ret = wglMakeCurrent(state.hdc, state.hrc); if (!ret) LOG("FAILED wglMakeCurrent: %s", pp::platform::windows::GetLastErrorAsString().c_str()); state.gl_thread = std::this_thread::get_id(); } state.gl_count++; } bool try_lock_async_render_context() { auto& state = retained_win32_async_render_context_state(); if (state.gl_count == 0 || state.gl_thread != std::this_thread::get_id()) { if (!state.gl_mutex.try_lock()) return false; const bool ret = wglMakeCurrent(state.hdc, state.hrc); if (!ret) LOG("FAILED wglMakeCurrent: %s", pp::platform::windows::GetLastErrorAsString().c_str()); state.gl_thread = std::this_thread::get_id(); } state.gl_count++; return true; } void unlock_async_render_context() { auto& state = retained_win32_async_render_context_state(); state.gl_count--; if (state.gl_count == 0) { wglMakeCurrent(0, 0); state.gl_mutex.unlock(); } } void swap_async_render_context() { SwapBuffers(retained_win32_async_render_context_state().hdc); } void enqueue_main_thread_task(std::packaged_task task) { if (auto* runtime = bound_runtime()) { runtime->main_thread_task(std::move(task)); return; } if (!task.valid()) return; task(); } void drain_main_thread_tasks() { if (auto* runtime = bound_runtime()) { runtime->drain_main_thread_tasks(); return; } } } // namespace pp::platform::windows namespace { static CONSOLE_SCREEN_BUFFER_INFO render_debug_console_info; [[nodiscard]] GLenum debug_severity_notification() noexcept { return static_cast(pp::renderer::gl::debug_severity_notification()); } [[nodiscard]] GLenum debug_severity_low() noexcept { return static_cast(pp::renderer::gl::debug_severity_low()); } [[nodiscard]] GLenum debug_severity_medium() noexcept { return static_cast(pp::renderer::gl::debug_severity_medium()); } [[nodiscard]] GLenum debug_severity_high() noexcept { return static_cast(pp::renderer::gl::debug_severity_high()); } void handle_gl_callback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { (void)source; (void)type; (void)id; (void)userParam; static std::map colors = { { debug_severity_notification(), static_cast(8) }, { debug_severity_low(), static_cast(8) }, { debug_severity_medium(), static_cast(FOREGROUND_GREEN | FOREGROUND_INTENSITY) }, { debug_severity_high(), static_cast(FOREGROUND_RED | FOREGROUND_INTENSITY) }, }; if (severity == debug_severity_high() || severity == debug_severity_medium() || severity == debug_severity_low()) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]); LOG("OPENGL: %.*s", length, message); FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE)); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), render_debug_console_info.wAttributes); #ifdef _DEBUG if (severity == debug_severity_high()) __debugbreak(); #endif } } void show_cursor(bool visible) { pp::platform::windows::enqueue_main_thread_task(std::packaged_task([=] { if (visible) while (ShowCursor(true) < 0); else while (ShowCursor(false) >= 0); })); } std::string clipboard_text() { std::string ret; if (OpenClipboard(pp_windows_main_window_handle())) { if (HANDLE h = GetClipboardData(CF_TEXT)) { if (char* s = static_cast(GlobalLock(h))) { ret = s; GlobalUnlock(h); } } CloseClipboard(); } return ret; } bool set_clipboard_text(const std::string& s) { bool success = false; if (OpenClipboard(pp_windows_main_window_handle())) { // owned by SetClipboardData if (HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1)) { if (char* p = static_cast(GlobalLock(h))) { std::copy(s.begin(), s.end(), p); p[s.size()] = 0; GlobalUnlock(h); success = true; } EmptyClipboard(); SetClipboardData(CF_TEXT, h); } CloseClipboard(); } return success; } std::string open_file(const char* filter) { OPENFILENAMEA ofn; char fileName[MAX_PATH] = ""; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = pp_windows_main_window_handle(); ofn.lpstrFilter = filter; ofn.lpstrFile = fileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; ofn.lpstrDefExt = ""; ofn.lpstrInitialDir = ""; if (GetOpenFileNameA(&ofn) != NULL) return fileName; return ""; } std::string save_file(const char* filter) { OPENFILENAMEA ofn; char fileName[MAX_PATH] = ""; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = pp_windows_main_window_handle(); ofn.lpstrFilter = filter; ofn.lpstrFile = fileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; ofn.lpstrDefExt = ""; ofn.lpstrInitialDir = ""; if (GetSaveFileNameA(&ofn) != NULL) return fileName; return ""; } std::string open_directory() { BROWSEINFOA bi; char Buffer[MAX_PATH]; ZeroMemory(Buffer, MAX_PATH); ZeroMemory(&bi, sizeof(bi)); bi.hwndOwner = pp_windows_main_window_handle(); bi.pszDisplayName = Buffer; bi.lpszTitle = "Title"; bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; LPCITEMIDLIST pFolder = SHBrowseForFolderA(&bi); if (pFolder == NULL) return ""; if (!SHGetPathFromIDListA(pFolder, Buffer)) return ""; return Buffer; } void invoke_selected_path( const std::string& path, const pp::platform::PickedPathCallback& callback) { if (!path.empty()) callback(path); } void ensure_directory(const std::string& path) { if (!PathFileExistsA(path.c_str())) CreateDirectoryA(path.c_str(), NULL); } [[nodiscard]] pp::platform::XrRuntimeSelection select_windows_xr_runtime() noexcept { // DEBT-0061: OpenXR is the target backend; Windows only exposes the retained OpenVR bridge today. return pp::platform::select_desktop_xr_runtime( false, true, true); } std::string build_supported_files_filter(const std::vector& types) { std::string filter = "Supported Files ("; bool first_type = true; for (const auto& t : types) { filter.append(std::string(first_type ? "" : " ,") + "*." + t); first_type = false; } filter.append(")"); filter.push_back(0); first_type = true; for (const auto& t : types) { filter.append(std::string(first_type ? "" : ";") + "*." + t); first_type = false; } filter.push_back(0); return filter; } class WindowsPlatformServices final : public pp::platform::PlatformServices { public: [[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths() override { std::string data_path; CHAR my_documents[MAX_PATH]; HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents); if (SUCCEEDED(result)) { data_path = std::string(my_documents) + "\\PanoPainter"; ensure_directory(data_path); } else { CHAR path[MAX_PATH]; GetCurrentDirectoryA(sizeof(path), path); data_path = path; } ensure_directory(data_path + "\\frames"); ensure_directory(data_path + "\\brushes"); ensure_directory(data_path + "\\brushes\\thumbs"); ensure_directory(data_path + "\\patterns"); ensure_directory(data_path + "\\patterns\\thumbs"); ensure_directory(data_path + "\\settings"); return { data_path, data_path, data_path + "\\frames", {}, }; } void log_stacktrace() override { } void trigger_crash_test() override { __debugbreak(); } [[nodiscard]] std::string clipboard_text() override { return ::clipboard_text(); } [[nodiscard]] bool set_clipboard_text(std::string_view text) override { return ::set_clipboard_text(std::string(text)); } void set_cursor_visible(bool visible) override { show_cursor(visible); } void set_virtual_keyboard_visible(bool visible) override { (void)visible; } void attach_ui_thread() override { } void detach_ui_thread() override { } void acquire_render_context() override { async_lock(); bind_default_render_target(); } void release_render_context() override { async_unlock(); } void present_render_context() override { win32_async_swap(); } void bind_default_render_target() override { pp::legacy::ui_gl::bind_opengl_framebuffer( pp::renderer::gl::framebuffer_target(), pp::renderer::gl::default_framebuffer_id()); } void bind_main_render_target() override { bind_default_render_target(); } void apply_render_platform_hints() override { const auto status = pp::renderer::gl::apply_opengl_render_platform_hints( pp::renderer::gl::OpenGlRenderPlatformHintDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, }); if (!status.ok()) LOG("OpenGL render platform hints failed: %s", status.message); } void install_render_debug_callback() override { if (!pp::legacy::gl_runtime::has_opengl_debug_message_callback()) return; // colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &render_debug_console_info); pp::legacy::gl_runtime::install_opengl_debug_message_callback(handle_gl_callback, nullptr); const auto status = pp::renderer::gl::apply_opengl_debug_output_states( pp::renderer::gl::OpenGlDebugOutputStateDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, }); if (!status.ok()) LOG("OpenGL debug output states failed: %s", status.message); } void begin_render_capture_frame() override { pp::platform::windows::win32_renderdoc_frame_start(); } void end_render_capture_frame() override { pp::platform::windows::win32_renderdoc_frame_end(); } [[nodiscard]] bool deletes_recorded_files_on_clear() override { return pp::platform::platform_deletes_recorded_files_on_clear(pp::platform::PlatformFamily::windows); } void clear_recorded_files(std::string_view recording_path) override { (void)recording_path; } void publish_exported_image(std::string_view path) override { if (!pp::platform::platform_publishes_exported_images(pp::platform::PlatformFamily::windows)) { (void)path; return; } (void)path; } void flush_persistent_storage() override { if (!pp::platform::platform_flushes_persistent_storage(pp::platform::PlatformFamily::windows)) return; } [[nodiscard]] std::vector document_browse_roots( std::string_view work_path, std::string_view data_path) override { return pp::platform::platform_document_browse_roots( pp::platform::PlatformFamily::windows, work_path, data_path); } void save_ui_state() override { if (!pp::platform::platform_saves_native_ui_state(pp::platform::PlatformFamily::windows)) return; win32_save_window_state(); } [[nodiscard]] bool enables_live_asset_reloading() override { return pp::platform::platform_enables_live_asset_reloading(pp::platform::PlatformFamily::windows); } void update_platform_frame(float delta_time_seconds) override { win32_update_stylus(delta_time_seconds); } void report_rendered_frames(int frames) override { win32_update_fps(frames); } void display_file(std::string_view path) override { (void)path; } void share_file(std::string_view path) override { (void)path; } void request_app_close() override { destroy_window(); } [[nodiscard]] bool start_vr_mode() override { const auto runtime = select_windows_xr_runtime(); if (runtime.backend == pp::platform::XrRuntimeBackend::openvr) { active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; if (win32_vr_start()) active_xr_runtime_backend_ = runtime.backend; return active_xr_runtime_backend_ == runtime.backend; } if (runtime.backend == pp::platform::XrRuntimeBackend::openxr) LOG("OpenXR runtime selected but the Windows OpenXR backend is not wired yet"); return false; } void stop_vr_mode() override { auto runtime = active_xr_runtime_backend_; if (runtime == pp::platform::XrRuntimeBackend::none) runtime = select_windows_xr_runtime().backend; if (runtime == pp::platform::XrRuntimeBackend::openvr) win32_vr_stop(); else if (runtime == pp::platform::XrRuntimeBackend::openxr) LOG("OpenXR runtime selected but the Windows OpenXR stop path is not wired yet"); active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; } void pick_image(pp::platform::PickedPathCallback callback) override { const std::string path = open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png"); invoke_selected_path(path, callback); } void pick_file(std::vector file_types, pp::platform::PickedPathCallback callback) override { const std::string filter = build_supported_files_filter(file_types); const std::string path = open_file(filter.c_str()); invoke_selected_path(path, callback); } void pick_save_file(std::vector file_types, pp::platform::PickedPathCallback callback) override { const std::string filter = build_supported_files_filter(file_types); const std::string path = save_file(filter.c_str()); invoke_selected_path(path, callback); } void pick_directory(pp::platform::PickedPathCallback callback) override { const std::string path = open_directory(); invoke_selected_path(path, callback); } [[nodiscard]] bool supports_working_directory_picker() override { return pp::platform::platform_supports_working_directory_picker(pp::platform::PlatformFamily::windows); } [[nodiscard]] std::string format_working_directory_path(std::string_view path) override { char path_buffer[MAX_PATH] = {}; const auto length = GetFullPathNameA( std::string(path).c_str(), static_cast(sizeof(path_buffer)), path_buffer, nullptr); if (length > 0 && length < sizeof(path_buffer)) return path_buffer; return std::string(path); } [[nodiscard]] bool uses_prepared_file_writes() override { return pp::platform::platform_uses_prepared_file_writes(pp::platform::PlatformFamily::windows); } [[nodiscard]] bool uses_work_directory_document_export_collections() override { return pp::platform::platform_uses_work_directory_document_export_collections( pp::platform::PlatformFamily::windows); } [[nodiscard]] bool disables_network_tls_verification() override { return pp::platform::default_disables_network_tls_verification(); } [[nodiscard]] bool uses_ppbr_export_data_directory_override() override { return pp::platform::platform_uses_ppbr_export_data_directory_override( pp::platform::PlatformFamily::windows); } [[nodiscard]] bool supports_sonarpen() override { return pp::platform::platform_supports_sonarpen(pp::platform::PlatformFamily::windows); } void start_sonarpen() override { } [[nodiscard]] int default_canvas_resolution() override { return pp::platform::platform_default_canvas_resolution(pp::platform::PlatformFamily::windows); } [[nodiscard]] bool draws_canvas_tip_for_pointer( bool is_mouse, bool is_stylus, bool is_left_button_release) override { return pp::platform::platform_draws_canvas_tip_for_pointer( pp::platform::PlatformFamily::windows, is_mouse, is_stylus, is_left_button_release); } [[nodiscard]] float adjust_canvas_input_pressure(float pressure) override { const auto curve = [](float x, float max) { return x > max ? 1.f : glm::pow(1.f - glm::pow(x / max - 1.f, 2.f), 2.f); }; return curve(pressure, 0.95f); } [[nodiscard]] pp::platform::PreparedFileTarget prepare_writable_file( std::string_view type, std::string_view default_name, std::string_view data_path, std::string_view temporary_path) override { return pp::platform::plan_platform_writable_file( pp::platform::PlatformFamily::windows, type, default_name, data_path, temporary_path); } void save_prepared_file( std::string_view path, std::string_view suggested_name, pp::platform::PreparedFileCallback callback) override { (void)suggested_name; callback(std::string(path), false); } private: pp::platform::XrRuntimeBackend active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; }; } namespace pp::platform::windows { PlatformServices& platform_services() { static WindowsPlatformServices services; return services; } VrSessionSnapshot read_platform_vr_session_snapshot() noexcept { return read_vr_session_snapshot(retained_state().vr); } }