#include "pch.h" #include "platform_windows/windows_platform_services.h" #include "log.h" #include "platform_api/network_tls_policy.h" #include "platform_api/platform_policy.h" #include "renderer_gl/opengl_capabilities.h" #include #include extern HWND hWnd; extern std::deque> main_tasklist; extern std::mutex main_task_mutex; void destroy_window(); void async_lock(); void async_unlock(); void win32_async_swap(); void win32_renderdoc_frame_start(); void win32_renderdoc_frame_end(); 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(); 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 enable_gl_capability(std::uint32_t state) noexcept { glEnable(static_cast(state)); } void show_cursor(bool visible) { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { if (visible) while (ShowCursor(true) < 0); else while (ShowCursor(false) >= 0); }); } std::string clipboard_text() { std::string ret; if (OpenClipboard(hWnd)) { 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(hWnd)) { // 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 = hWnd; 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 = hWnd; 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 = hWnd; 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(); glBindFramebuffer( static_cast(pp::renderer::gl::framebuffer_target()), static_cast(pp::renderer::gl::default_framebuffer_id())); } void release_render_context() override { async_unlock(); } void present_render_context() override { win32_async_swap(); } void bind_default_render_target() override { glBindFramebuffer( static_cast(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 = enable_gl_capability, }); if (!status.ok()) LOG("OpenGL render platform hints failed: %s", status.message); } void install_render_debug_callback() override { if (!glDebugMessageCallback) return; // colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &render_debug_console_info); glDebugMessageCallback(handle_gl_callback, nullptr); const auto status = pp::renderer::gl::apply_opengl_debug_output_states( pp::renderer::gl::OpenGlDebugOutputStateDispatch { .enable = enable_gl_capability, }); if (!status.ok()) LOG("OpenGL debug output states failed: %s", status.message); } void begin_render_capture_frame() override { win32_renderdoc_frame_start(); } void end_render_capture_frame() override { 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; } }