#include #include #include #include #if defined(PSEMEK_GRAPHICS_API_OPENGL) #include #include #elif defined(PSEMEK_GRAPHICS_API_WEBGPU) #include #include #include #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); auto 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 }}; #else surface_descriptor.chain = {wgpu::surface::from_xlib_window{ .display = wminfo.info.x11.display, .window = wminfo.info.x11.window }}; #endif wgpu_surface_ = instance.create_surface(surface_descriptor); wgpu::adapter::request_options adapter_request_options; adapter_request_options.compatible_surface = wgpu_surface_; adapter_request_options.backend_type = wgpu::backend_type::undefined; instance.request_adapter(adapter_request_options, [this](wgpu::adapter::request_status status, wgpu::adapter adapter_in, std::string const & message) { if (status != wgpu::adapter::request_status::success) throw std::runtime_error("Failed to request WebGPU adapter: " + message); wgpu_adapter_ = std::move(adapter_in); }); wgpu::device::descriptor device_descriptor { .required_features = options.required_features, .required_limits = std::nullopt, }; 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(device_descriptor, [this](wgpu::device::request_status status, wgpu::device device_in, std::string const & message) { if (status != wgpu::device::request_status::success) throw std::runtime_error("Failed to request WebGPU device: " + message); wgpu_device_ = std::move(device_in); }); auto adapter_properties = wgpu_adapter_.get_properties(); std::string adapter_backend_str; switch (adapter_properties.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_properties.name << ", " << adapter_backend_str << " backend"; log::info() << "Using wgpu-native version " << wgpu::get_version(); #endif } geom::vector window::size() const { geom::vector result; SDL_GL_GetDrawableSize(window_, &result[0], &result[1]); return result; } void window::show() { SDL_ShowWindow(window_); } void window::swap() { SDL_GL_SwapWindow(window_); } 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(bool on) { 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); } } 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); geom::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; geom::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_); } }