#include #include #include #include #if defined(PSEMEK_GRAPHICS_API_OPENGL) #include #include #elif defined(PSEMEK_GRAPHICS_API_WEBGPU) #include #include #include #ifdef __APPLE__ extern "C" void * metal_layer_from_nswindow(void * nswindow); #endif #endif #include namespace psemek::sdl2 { window::window(psemek::app::application::options const & options) : sdl_init_(init(SDL_INIT_EVENTS | SDL_INIT_VIDEO)) { std::uint32_t flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; if (options.highdpi) flags |= SDL_WINDOW_ALLOW_HIGHDPI; #if defined(PSEMEK_GRAPHICS_API_OPENGL) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl::sys::major_version()); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::minor_version()); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); if (options.multisampling == 0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); } else { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, options.multisampling); } flags |= SDL_WINDOW_OPENGL; #endif SDL_DisplayMode display_mode; SDL_GetCurrentDisplayMode(0, &display_mode); int x = SDL_WINDOWPOS_UNDEFINED; int y = SDL_WINDOWPOS_UNDEFINED; if (false) { display_mode.w = 1024; display_mode.h = 576; flags ^= SDL_WINDOW_RESIZABLE; x = 0; y = 0; } window_ = SDL_CreateWindow(options.name.data(), x, y, display_mode.w, display_mode.h, flags); if (!window_) sdl2::fail("Failed to create window: "); #if defined(PSEMEK_GRAPHICS_API_OPENGL) gl_context_ = SDL_GL_CreateContext(window_); if (!gl_context_) sdl2::fail("Failed to create OpenGL context: "); SDL_GL_MakeCurrent(window_, gl_context_); gfx::init(); #elif defined(PSEMEK_GRAPHICS_API_WEBGPU) wgpu::setup_logging(log::level::error); wgpu_instance_ = wgpu::instance::create({}); SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); SDL_GetWindowWMInfo(window_, &wminfo); wgpu::surface::descriptor surface_descriptor; #if defined(_WIN32) surface_descriptor.chain = {wgpu::surface::from_windows_hwnd{ .hinstance = wminfo.info.win.hinstance, .hwnd = wminfo.info.win.window }}; #elif defined(__linux__) surface_descriptor.chain = {wgpu::surface::from_xlib_window{ .display = wminfo.info.x11.display, .window = wminfo.info.x11.window }}; #elif defined(__APPLE__) surface_descriptor.chain = {wgpu::surface::from_metal_layer{ .layer = metal_layer_from_nswindow(wminfo.info.cocoa.window) }}; #else #error Unknown platform #endif wgpu_surface_ = wgpu_instance_.create_surface(surface_descriptor); wgpu::adapter::request_options adapter_request_options; // adapter_request_options.feature_level = wgpu::feature_level::compatibility; adapter_request_options.compatible_surface = wgpu_surface_; adapter_request_options.backend_type = wgpu::backend_type::undefined; wgpu_instance_.request_adapter(wgpu::callback_mode::allow_process_events, adapter_request_options, [this](wgpu::adapter::request_status status, wgpu::adapter adapter_in, std::string_view message) { if (status != wgpu::adapter::request_status::success) throw std::runtime_error("Failed to request WebGPU adapter: " + std::string(message)); wgpu_adapter_ = std::move(adapter_in); }); [[maybe_unused]] auto adapter_limits = wgpu_adapter_.get_limits(); wgpu::device::descriptor device_descriptor { .required_features = options.required_features, .required_limits = std::nullopt, .lost_callback = [](wgpu::device::lost_reason reason, std::string_view message) { std::ostringstream os; os << "WebGPU device lost, reason: " << static_cast(reason) << ", message: " << message; auto str = os.str(); log::error() << str; // This will most probably panic in wgpu-native internals throw util::exception(std::move(str)); }, .lost_callback_mode = wgpu::callback_mode::allow_process_events, .uncaptured_error_callback = [](wgpu::error_type type, std::string_view message) { std::ostringstream os; os << "Uncaptured WebGPU error, type: " << static_cast(type) << ", message: " << message; auto str = os.str(); log::error() << str; // This will most probably panic in wgpu-native internals throw util::exception(std::move(str)); }, }; if (options.required_limits || options.required_native_limits) device_descriptor.required_limits = wgpu::device::required_limits{}; if (options.required_limits) device_descriptor.required_limits->limits = *options.required_limits; if (options.required_native_limits) device_descriptor.required_limits->chain.push_back(wgpu::native_limits{*options.required_native_limits}); wgpu_adapter_.request_device(wgpu::callback_mode::allow_process_events, device_descriptor, [this](wgpu::device::request_status status, wgpu::device device_in, std::string_view message) { if (status != wgpu::device::request_status::success) throw std::runtime_error("Failed to request WebGPU device: " + std::string(message)); wgpu_device_ = std::move(device_in); }); [[maybe_unused]] auto device_limits = wgpu_device_.get_limits(); auto adapter_info = wgpu_adapter_.get_info(); std::string adapter_backend_str; switch (adapter_info.backend_type) { case wgpu::backend_type::undefined: adapter_backend_str = "undefined"; break; case wgpu::backend_type::null: adapter_backend_str = "(null)"; break; case wgpu::backend_type::webgpu: adapter_backend_str = "WebGPU"; break; case wgpu::backend_type::d3d11: adapter_backend_str = "D3D11"; break; case wgpu::backend_type::d3d12: adapter_backend_str = "D3D12"; break; case wgpu::backend_type::metal: adapter_backend_str = "Metal"; break; case wgpu::backend_type::vulkan: adapter_backend_str = "Vulkan"; break; case wgpu::backend_type::opengl: adapter_backend_str = "OpenGL"; break; case wgpu::backend_type::opengles: adapter_backend_str = "OpenGL ES"; break; } log::info() << "Initialized WebGPU: " << adapter_info.device << ", " << adapter_backend_str << " backend"; log::info() << "Using wgpu-native version " << wgpu::get_version(); #endif } math::vector window::size() const { math::vector result; SDL_GL_GetDrawableSize(window_, &result[0], &result[1]); return result; } void window::show() { SDL_ShowWindow(window_); } void window::swap() { #if defined(PSEMEK_GRAPHICS_API_OPENGL) SDL_GL_SwapWindow(window_); #endif } void window::show_cursor(bool show) { SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE); } void window::relative_mouse_mode(bool mode) { log::info() << "Relative mouse mode " << (mode ? "on" : "off"); SDL_SetRelativeMouseMode(mode ? SDL_TRUE: SDL_FALSE); } void window::vsync([[maybe_unused]] bool on) { #if defined(PSEMEK_GRAPHICS_API_OPENGL) log::info() << "Turning VSync " << (on ? "on" : "off"); if (on) { // try adaptive vsync if (SDL_GL_SetSwapInterval(-1) != 0) { // failed, try usual vsync then SDL_GL_SetSwapInterval(1); } } else { SDL_GL_SetSwapInterval(0); } #endif } void window::windowed(bool on) { log::info() << "Entering " << (on ? "windowed" : "fullscreen") << " mode"; SDL_SetWindowFullscreen(window_, on ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP); SDL_SetWindowBordered(window_, on ? SDL_TRUE : SDL_FALSE); SDL_RestoreWindow(window_); if (on) { SDL_Rect display_usable_bounds; SDL_GetDisplayUsableBounds(0, &display_usable_bounds); int top_border, left_border, bottom_border, right_border; SDL_GetWindowBordersSize(window_, &top_border, &left_border, &bottom_border, &right_border); math::box bounds_rect; bounds_rect[0] = {display_usable_bounds.x, display_usable_bounds.x + display_usable_bounds.w}; bounds_rect[1] = {display_usable_bounds.y, display_usable_bounds.y + display_usable_bounds.h}; log::info() << "Display usable bounds: " << bounds_rect; log::info() << "Window borders: left " << left_border << ", right " << right_border << ", top " << top_border << ", bottom " << bottom_border; math::box window_rect = bounds_rect; window_rect[0].min += left_border; window_rect[0].max -= right_border; window_rect[1].min += top_border; window_rect[1].max -= bottom_border; log::info() << "Computed window rect: " << window_rect; SDL_SetWindowSize(window_, window_rect.dimensions()[0], window_rect.dimensions()[1]); SDL_SetWindowPosition(window_, window_rect[0].min, window_rect[1].min); } } window::~window() { #if defined(PSEMEK_SDL2_OPENGL) if (gl_context_) SDL_GL_DeleteContext(gl_context_); #endif if (window_) SDL_DestroyWindow(window_); } }