/******************************************************************************/ #include "stdafx.h" namespace EE{ /******************************************************************************/ #define D3D_DEBUG 0 #define FORCE_D3D9_3 0 #define FORCE_D3D10_0 0 #define FORCE_D3D10_1 0 #define MAC_GL_MT 1 // enable multi threaded rendering, TODO: test on newer hardware if improves performance, all GL Contexts should have the same value, otherwise creating VAO's from VBO's on other threads could fail #if D3D_DEBUG || FORCE_D3D9_3 || FORCE_D3D10_0 || FORCE_D3D10_1 #pragma message("!! Warning: Use this only for debugging !!") #endif #define FORCE_MAIN_DISPLAY (DX9 || WINDOWS && GL) // for DX9 and WindowsOpenGL we need to use the main screen #if 0 #define LOG(x) LogN(x) #else #define LOG(x) #endif #define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF /******************************************************************************/ Display D; #if DX9 // DirectX 9 static IDirect3D9 *D3DBase; IDirect3DDevice9 *D3D; static IDirect3DQuery9 *Query; static D3DPRESENT_PARAMETERS D3DPP; #elif DX11 // DirectX 10/11 #define GDI_COMPATIBLE 0 // requires DXGI_FORMAT_B8G8R8A8_UNORM static Bool AllowTearing; static UInt PresentFlags; ID3D11Device *D3D; ID3D11DeviceContext *D3DC; ID3D11DeviceContext1 *D3DC1; #if WINDOWS_OLD IDXGIFactory1 *Factory; IDXGISwapChain *SwapChain; DXGI_SWAP_CHAIN_DESC SwapChainDesc; #else IDXGIFactory2 *Factory; IDXGISwapChain1 *SwapChain; DXGI_SWAP_CHAIN_DESC1 SwapChainDesc; #endif IDXGIAdapter *Adapter; IDXGIOutput *Output; ID3D11Query *Query; D3D_FEATURE_LEVEL FeatureLevel; #elif GL // OpenGL #if WINDOWS static HDC hDC; #elif MAC NSOpenGLContext *OpenGLContext; #elif LINUX typedef void (*glXSwapIntervalType)(::Display *display, GLXDrawable drawable, int interval); static glXSwapIntervalType glXSwapInterval; ::Display *XDisplay; GLXFBConfig GLConfig; static int vid_modes=0; static XF86VidModeModeInfo **vid_mode=null; #elif ANDROID static EGLConfig GLConfig; static EGLDisplay GLDisplay; #endif GLContext MainContext; static Mems SecondaryContexts; static SyncLock ContextLock; static SyncCounter ContextUnlocked; // have to use counter and not event, because if there would be 2 unlocks at the same time while 2 other are waiting, then only 1 would get woken up with event UInt FBO, VAO; #if VARIABLE_MAX_MATRIX Bool MeshBoneSplit; #endif #endif #if WINDOWS_NEW Flt ScreenScale; // can't initialize here because crash will occur #elif IOS Flt ScreenScale=([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)] ? [UIScreen mainScreen].nativeScale : [UIScreen mainScreen].scale); #endif /******************************************************************************/ static Bool ActualSync() {return D.sync() && !VR.active();} // we can synchronize only when not using VR, otherwise, it will handle synchronization based on the VR refresh rate /******************************************************************************/ static Bool CustomMode; #if MAC extern NSOpenGLView *OpenGLView; static CGDisplayModeRef GetDisplayMode(Int width, Int height) { CFArrayRef modes=CGDisplayCopyAllDisplayModes(CGMainDisplayID(), null); CGDisplayModeRef ret=null; Flt refresh; REP(CFArrayGetCount(modes)) { CGDisplayModeRef mode=(CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); UInt flags=CGDisplayModeGetIOFlags(mode); Bool ok =FlagTest(flags, kDisplayModeSafetyFlags); if( ok) { Int w=CGDisplayModeGetWidth (mode), h=CGDisplayModeGetHeight (mode); Flt r=CGDisplayModeGetRefreshRate(mode); if(w==width && h==height) if(!ret || r>refresh) // choose highest refresh rate { ret =mode; refresh=r; } } } CFRelease(modes); return ret; } #endif Bool SetDisplayMode(Int mode) { Bool full=(D.full() && (mode==2 || mode==1 && App.active())); VecI2 size=(full ? D.res() : App.desktop()); Bool same=(D.screen()==size); #if WINDOWS_OLD if(full) { if(same)return true; DEVMODE screen; Zero(screen); screen.dmSize =SIZE(DEVMODE); screen.dmPelsWidth =D.resW(); screen.dmPelsHeight =D.resH(); screen.dmDisplayFixedOutput=DMDFO_STRETCH; // this will stretch to entire screen if aspect ratio is not the same screen.dmFields =DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFIXEDOUTPUT; again: Int result=ChangeDisplaySettings(&screen, CDS_FULLSCREEN); if(result==DISP_CHANGE_SUCCESSFUL) { CustomMode=true; return true; } if(result==DISP_CHANGE_BADMODE && (screen.dmFields&DM_DISPLAYFIXEDOUTPUT)) // this can fail if trying to set the biggest resolution with stretching {FlagDisable(screen.dmFields, DM_DISPLAYFIXEDOUTPUT); screen.dmDisplayFixedOutput=0; goto again;} // try again without scaling }else { if(!CustomMode)return true; if(ChangeDisplaySettings(null, 0)==DISP_CHANGE_SUCCESSFUL) { CustomMode=false; return true; } } #elif MAC if(OpenGLView) { REPA(Ms._button)Ms.release(i); // Mac will not detect mouse button release if it was pressed during screen changing, so we need to disable it manually if([OpenGLView isInFullScreenMode])[OpenGLView exitFullScreenModeWithOptions:nil]; // set screen mode Bool ok=false; if(same)ok=true;else if(CGDisplayModeRef mode=GetDisplayMode(size.x, size.y)) ok=(CGDisplaySetDisplayMode(kCGDirectMainDisplay, mode, null)==noErr); if(ok && full) { #if 0 ok=[OpenGLView enterFullScreenMode:[NSScreen mainScreen] withOptions:nil]; #else NSApplicationPresentationOptions options=NSApplicationPresentationHideDock|NSApplicationPresentationHideMenuBar; ok=[OpenGLView enterFullScreenMode:[NSScreen mainScreen] withOptions:@{NSFullScreenModeApplicationPresentationOptions:@(options)}]; #endif } return ok; } #elif LINUX if(XDisplay) { // set screen mode Bool ok=false; if(same)ok=true;else FREP(vid_modes) { XF86VidModeModeInfo &mode=*vid_mode[i]; if(mode.hdisplay==size.x && mode.vdisplay==size.y) if(XF86VidModeSwitchToMode(XDisplay, DefaultScreen(XDisplay), &mode) && XFlush(XDisplay)){ok=true; break;} } return ok; } #endif return false; } #if WINDOWS_NEW void RequestDisplayMode(Int w, Int h, Int full) { if(full> 0)Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->TryEnterFullScreenMode();else if(full==0)Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->ExitFullScreenMode (); if(w>0 || h>0) { if(w<=0)w=D.resW(); if(h<=0)h=D.resH(); Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->TryResizeView(Windows::Foundation::Size(PixelsToDips(w), PixelsToDips(h))); } } #endif #if WINDOWS && GL static void glewSafe() { #define V(x, y, z) {if(!x)x=y; if(!x)Exit("OpenGL " z " function not supported.\nGraphics Driver not installed or better video card is required.");} V(glGenRenderbuffers , glGenRenderbuffersEXT , "glGenRenderbuffers") V(glDeleteRenderbuffers , glDeleteRenderbuffersEXT , "glDeleteRenderbuffers") V(glRenderbufferStorage , glRenderbufferStorageEXT , "glRenderbufferStorage") V(glGetRenderbufferParameteriv, glGetRenderbufferParameterivEXT, "glGetRenderbufferParameteriv") V(glBindRenderbuffer , glBindRenderbufferEXT , "glBindRenderbuffer") V(glGenFramebuffers , glGenFramebuffersEXT , "glGenFramebuffers") V(glDeleteFramebuffers , glDeleteFramebuffersEXT , "glDeleteFramebuffers") V(glBindFramebuffer , glBindFramebufferEXT , "glBindFramebuffer") V(glBlitFramebuffer , glBlitFramebufferEXT , "glBlitFramebuffer") #if DEBUG V(glCheckFramebufferStatus , glCheckFramebufferStatusEXT , "glCheckFramebufferStatus") #endif V(glFramebufferTexture2D , glFramebufferTexture2DEXT , "glFramebufferTexture2D") V(glFramebufferRenderbuffer , glFramebufferRenderbufferEXT , "glFramebufferRenderbuffer") V(glBlendColor , glBlendColorEXT , "glBlendColor") V(glBlendEquation , glBlendEquationEXT , "glBlendEquation") V(glBlendEquationSeparate , glBlendEquationSeparateEXT , "glBlendEquationSeparate") V(glBlendFuncSeparate , glBlendFuncSeparateEXT , "glBlendFuncSeparate") V(glColorMaski, glColorMaskIndexedEXT, "glColorMaski") #undef V } #endif /******************************************************************************/ // GL CONTEXT /******************************************************************************/ #if GL static Ptr GetCurrentContext() { #if WINDOWS return wglGetCurrentContext(); #elif MAC return CGLGetCurrentContext(); #elif LINUX return glXGetCurrentContext(); #elif ANDROID return eglGetCurrentContext(); #elif IOS return [EAGLContext currentContext]; #elif WEB return (Ptr)emscripten_webgl_get_current_context(); #endif } Bool GLContext::is()C { #if WEB return context!=NULL; #else return context!=null; #endif } GLContext::GLContext() { locked=false; #if WEB context=NULL; #else context=null; #endif #if ANDROID surface=null; #endif } void GLContext::del() { if(context) { #if WINDOWS wglMakeCurrent(null, null); wglDeleteContext(context); context=null; #elif MAC CGLDestroyContext(context); context=null; #elif LINUX if(XDisplay){glXMakeCurrent(XDisplay, NULL, NULL); glXDestroyContext(XDisplay, context);} context=null; #elif ANDROID if(GLDisplay){eglMakeCurrent(GLDisplay, null, null, null); eglDestroyContext(GLDisplay, context);} context=null; #elif IOS [EAGLContext setCurrentContext:null]; [context release]; context=null; #elif WEB emscripten_webgl_destroy_context(context); context=NULL; #endif } #if ANDROID if(surface){if(GLDisplay)eglDestroySurface(GLDisplay, surface); surface=null;} #endif } Bool GLContext::createSecondary() { #if WINDOWS if(context=wglCreateContext(hDC))if(!wglShareLists(MainContext.context, context))return false; #elif MAC CGLCreateContext(CGLGetPixelFormat(MainContext.context), MainContext.context, &context); #elif LINUX context=glXCreateNewContext(XDisplay, GLConfig, GLX_RGBA_TYPE, MainContext.context, true); #elif ANDROID EGLint attribs[]={EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; if(surface=eglCreatePbufferSurface(GLDisplay, GLConfig, attribs)) { EGLint ctx_attribs[]={EGL_CONTEXT_CLIENT_VERSION, (D.shaderModel()==SM_GL_ES_3) ? 3 : 2, EGL_NONE}; context=eglCreateContext(GLDisplay, GLConfig, MainContext.context, ctx_attribs); } #elif IOS context=[[EAGLContext alloc] initWithAPI:[MainContext.context API] sharegroup:[MainContext.context sharegroup]]; #elif WEB // currently WEB is not multi-threaded #if HAS_THREADS add support #endif #endif if(context) { lock(); // these settings are per-context #if MAC && MAC_GL_MT Bool mt_ok=(CGLEnable(context, kCGLCEMPEngine)!=kCGLNoError); #endif glPixelStorei(GL_PACK_ALIGNMENT , 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); unlock(); // to clear 'locked' return true; } return false; } void GLContext::lock() { #if WINDOWS if(wglMakeCurrent(hDC, context)) #elif MAC if(CGLSetCurrentContext(context)==kCGLNoError) #elif LINUX if(glXMakeCurrent(XDisplay, App.Hwnd(), context)) #elif ANDROID if(eglMakeCurrent(GLDisplay, surface, surface, context)==EGL_TRUE) #elif IOS if([EAGLContext setCurrentContext:context]==YES) #elif WEB if(emscripten_webgl_make_context_current(context)==EMSCRIPTEN_RESULT_SUCCESS) #endif { locked=true; }else { Exit("Can't activate OpenGL Context."); } } void GLContext::unlock() { #if WINDOWS if(wglMakeCurrent(hDC, null)) #elif MAC if(CGLSetCurrentContext(null)==kCGLNoError) #elif LINUX if(glXMakeCurrent(XDisplay, NULL, NULL)) #elif ANDROID if(eglMakeCurrent(GLDisplay, null, null, null)==EGL_TRUE) #elif IOS if([EAGLContext setCurrentContext:null]==YES) #elif WEB if(emscripten_webgl_make_context_current(NULL)==EMSCRIPTEN_RESULT_SUCCESS) #endif { locked=false; }else { Exit("Can't deactivate OpenGL Context."); } } #endif /******************************************************************************/ // DISPLAY /******************************************************************************/ VecI2 Display::screen()C { #if WINDOWS_OLD return VecI2(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); #elif WINDOWS_NEW IDXGIOutput *output=null; if(SwapChain) { SyncLocker locker(_lock); if(SwapChain)SwapChain->GetContainingOutput(&output); } if(!output) { IDXGIFactory1 *factory=null; CreateDXGIFactory1(__uuidof(IDXGIFactory1), (Ptr*)&factory); if(factory) { IDXGIAdapter *adapter=null; factory->EnumAdapters(0, &adapter); if(adapter) { adapter->EnumOutputs(0, &output); adapter->Release(); } factory->Release(); } } if(output) { VecI2 size=-1; DXGI_OUTPUT_DESC desc; if(OK(output->GetDesc(&desc)))size.set(desc.DesktopCoordinates.right-desc.DesktopCoordinates.left, desc.DesktopCoordinates.bottom-desc.DesktopCoordinates.top); output->Release(); if(size.x>=0)return size; } #elif MAC if(CGDisplayModeRef mode=CGDisplayCopyDisplayMode(kCGDirectMainDisplay)) { VecI2 s(CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode)); CGDisplayModeRelease(mode); return s; } #elif LINUX if(XDisplay) { int clock; XF86VidModeModeLine mode; if(XF86VidModeGetModeLine(XDisplay, DefaultScreen(XDisplay), &clock, &mode))return VecI2(mode.hdisplay, mode.vdisplay); Screen *screen=DefaultScreenOfDisplay(XDisplay); return VecI2(WidthOfScreen(screen), HeightOfScreen(screen)); } #elif ANDROID // fall down to the App.desktop() #elif IOS CGSize size; if([[UIScreen mainScreen] respondsToSelector:@selector(nativeBounds)]) { size=[[UIScreen mainScreen] nativeBounds].size; // 'nativeBounds' is not changed when device is rotated }else { size=[[UIScreen mainScreen] bounds].size; // 'bounds' is changed when device is rotated size.width *=ScreenScale; size.height*=ScreenScale; } return VecI2(RoundPos(size.width), RoundPos(size.height)); #elif WEB return VecI2(JavaScriptRunI("screen.width"), JavaScriptRunI("screen.height")); // it's not possible to get correct results, because on Chrome: this value is adjusted by "System DPI/Scaling", but not 'D.browserZoom', and does not change when zooming. Because "System DPI/Scaling" is unknown, it can't be calculated. #endif return App.desktop(); // this is not changed when device is rotated (obtained at app startup) } /******************************************************************************/ void Display::setShader(C Material *material) { if(created()) { _set_shader_material=material; {Meshes .lock(); REPA(Meshes )Meshes .lockedData(i).setShader(); Meshes .unlock();} {ClothMeshes.lock(); REPA(ClothMeshes)ClothMeshes.lockedData(i).setShader(); ClothMeshes.unlock();} if(set_shader)set_shader(); _set_shader_material=null; } } Display& Display::drawNullMaterials(Bool on) { if(_draw_null_mtrl!=on) { _draw_null_mtrl=on; setShader(); } return T; } void Display::screenChanged(Flt old_width, Flt old_height) { if(old_width>0 && old_height>0) { Gui.screenChanged(old_width, old_height); if(screen_changed)screen_changed(old_width, old_height); } } #if DX9 Bool Display::validUsage(UInt usage, D3DRESOURCETYPE res_type, Int image_type) { if(D3DBase) { D3DFORMAT format=ImageTypeToFormat(image_type); return OK(D3DBase->CheckDeviceFormat(D3DADAPTER_DEFAULT, _no_gpu ? D3DDEVTYPE_NULLREF : D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, usage, res_type, format)) || OK(D3DBase->CheckDeviceFormat(D3DADAPTER_DEFAULT, _no_gpu ? D3DDEVTYPE_NULLREF : D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, usage, res_type, format)); } return false; } #endif Str8 Display::shaderModelName()C { switch(shaderModel()) { default : return "Unknown"; // SM_UNKNOWN case SM_GL_ES_2: return "GL ES 2"; case SM_GL_ES_3: return "GL ES 3"; case SM_GL : return "GL"; case SM_3 : return "3"; case SM_4 : return "4"; case SM_4_1 : return "4.1"; case SM_5 : return "5"; } } Str8 Display::apiName()C { #if DX9 return "DirectX 9"; #elif DX11 return "DirectX 11"; #elif DX12 return "DirectX 12"; #elif METAL return "Metal"; #elif VULKAN return "Vulkan"; #elif WEB // check this first before 'GL' and 'GL_ES' return "Web GL"; #elif GL_ES // check this first before 'GL' return "OpenGL ES"; #elif GL return "OpenGL"; #endif } Bool Display::smallSize()C { #if WINDOWS_NEW Dbl inches; if(OK(GetIntegratedDisplaySize(&inches)))return inches<7; #elif ANDROID Int size=((AndroidApp && AndroidApp->config) ? AConfiguration_getScreenSize(AndroidApp->config) : ACONFIGURATION_SCREENSIZE_NORMAL); return size==ACONFIGURATION_SCREENSIZE_SMALL || size==ACONFIGURATION_SCREENSIZE_NORMAL; // HTC EVO 3D ( 4 inch) has ACONFIGURATION_SCREENSIZE_NORMAL // Samsung Galaxy Note 2 ( 5.5 inch) has ACONFIGURATION_SCREENSIZE_NORMAL // Asus Transformer Prime (10 inch) has ACONFIGURATION_SCREENSIZE_XLARGE #elif IOS // UI_USER_INTERFACE_IDIOM UIUserInterfaceIdiomPhone UIUserInterfaceIdiomPad return UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone; #endif return false; } Flt Display::browserZoom()C { #if WEB return emscripten_get_device_pixel_ratio(); #else return 1; #endif } Bool Display::deferredUnavailable ()C {return created() && _max_rt<2 ;} // deferred requires at least 2 MRT's (#0 Color, #1 Nrm, #2 Vel optional) Bool Display::deferredMSUnavailable()C {return created() && shaderModel()full; work=monitor->work; }else { full.set(0, 0, App.desktopW(), App.desktopH()); work=App.desktopArea(); } max_normal_win_client_size.set(full.w()-App._bound.w(), full.h()-App._bound.h()); maximized_win_client_size.set(work.w()+App._bound_maximized.min.x+App._bound_maximized.max.x, work.h()+App._bound_maximized.min.y+App._bound_maximized.max.y); } void Display::curMonitor(RectI &full, RectI &work, VecI2 &max_normal_win_client_size, VecI2 &maximized_win_client_size) { Monitor *monitor=null; #if WINDOWS_OLD if(App.hwnd()) // adjust to current monitor { #if 1 if(HMONITOR hmonitor=MonitorFromWindow(App.Hwnd(), MONITOR_DEFAULTTONEAREST)) #else RectI win_rect=WindowRect(false); // watch out because 'WindowRect' can return weird position when the window is minimized POINT p; p.x=win_rect.centerXI(); p.y=win_rect.centerYI(); if(HMONITOR hmonitor=MonitorFromPoint(p, MONITOR_DEFAULTTONEAREST)) #endif monitor=_monitors.get(hmonitor); } #elif WINDOWS_NEW if(SwapChain) { IDXGIOutput *output=null; { SyncLocker locker(_lock); if(SwapChain)SwapChain->GetContainingOutput(&output); } if(output) { DXGI_OUTPUT_DESC desc; if(OK(output->GetDesc(&desc))) { monitor=_monitors.get(desc.Monitor); if(monitor && !monitor->full.w())monitor->set(desc); } output->Release(); } } #endif T.monitor(full, work, max_normal_win_client_size, maximized_win_client_size, monitor); } void Display::mainMonitor(RectI &full, RectI &work, VecI2 &max_normal_win_client_size, VecI2 &maximized_win_client_size)C { C Monitor *main=null; REPA(_monitors){C Monitor &monitor=_monitors[i]; if(monitor.primary){main=&monitor; break;}} monitor(full, work, max_normal_win_client_size, maximized_win_client_size, main); } /******************************************************************************/ // MANAGE /******************************************************************************/ Display::Display() : _monitors(Compare, Create, null, 4) { _full =MOBILE; // by default request fullscreen for MOBILE, on WINDOWS_PHONE this will hide the navigation bar _sync =true; _exclusive =true; _hp_col_rt =false; _hp_nrm_rt =false; _hp_lum_rt =false; _hp_nrm_calc =true; _dither =true; _mtrl_blend =true; _device_mem =-1; _monitor_prec =IMAGE_PRECISION_8; _lit_col_rt_prec =IMAGE_PRECISION_8; _aspect_mode =(MOBILE ? ASPECT_SMALLER : ASPECT_Y); _tex_filter =(MOBILE ? 2 : 16); _tex_mip_filter =!MOBILE; _tex_detail =(MOBILE ? TEX_USE_DISABLE : TEX_USE_MULTI); _density_filter =(MOBILE ? FILTER_LINEAR : FILTER_CUBIC_FAST); _tex_lod =0; _tex_macro =true; _tex_reflect =TEX_USE_MULTI; _font_sharpness =0.75f; _bend_leafs =true; _particles_soft =!MOBILE; _particles_smooth=!MOBILE; _initialized=false; _resetting =false; _began =false; _allow_stereo=true; _density=127; _samples=1; _scale=1; _aspect_ratio=_aspect_ratio_want=_pixel_aspect=0; _pixel_size=_pixel_size_2=_pixel_size_inv=0; _window_pixel_to_screen_mul=1; // init to 1 to avoid div by 0 at app startup which could cause crash on Web _window_pixel_to_screen_add=0; _window_pixel_to_screen_scale=1; _amb_mode =AMBIENT_FLAT; _amb_soft =1; _amb_jitter =true; _amb_normal =true; _amb_res =FltToByteScale(0.5f); _amb_color =0.4f; _amb_contrast=1.2f; _amb_range =0.3f; _amb_scale =2.5f; _amb_bias =0.3f; _ns_color.zero(); _vol_light=false; _vol_add =false; _vol_max =1.0; _shd_mode =(MOBILE ? SHADOW_NONE : SHADOW_MAP); _shd_soft =0; _shd_jitter =false; _shd_reduce =false; _shd_frac =1; _shd_fade =1; _shd_map_num =6; _shd_map_size =1024; _shd_map_size_actual=0; _shd_map_size_l =1; _shd_map_size_c =1; _shd_map_split .set(2, 1); _cld_map_size =128; _bump_mode=(MOBILE ? BUMP_FLAT : BUMP_PARALLAX); _mtn_mode =MOTION_NONE; _mtn_dilate=DILATE_ORTHO2; _mtn_scale =0.04f; _mtn_res =FltToByteScale(1.0f/3); _dof_mode =DOF_NONE; _dof_foc_mode =false; //_dof_focus =0; _dof_range =30; _dof_intensity=1; _eye_adapt =false; _eye_adapt_brightness=0.37f; _eye_adapt_max_dark =0.5f; _eye_adapt_max_bright=2.0f; _eye_adapt_speed =6.5f; _eye_adapt_weight.set(0.9f, 1, 0.7f); // use smaller value for blue, to make blue skies have brighter multiplier, because human eye sees blue color as darker than others _grass_range =50; _grass_density=(MOBILE ? 0.5f : 1); _grass_shadow =false; _grass_mirror =false; _bloom_original=1.0f; _bloom_scale =0.5f; _bloom_cut =0.3f; _bloom_blurs =1; _bloom_sat =false; _bloom_max =false; _bloom_half =!MOBILE; _bloom_samples =!MOBILE; _bloom_allow=!MOBILE; _glow_allow=!MOBILE; _color_palette_allow=!MOBILE; _lod_factor =1; _lod_factor_shadow=2; _lod_factor_mirror=2; _tesselation =false; _tesselation_allow =true; _tesselation_heightmap=false; _tesselation_density =60; _outline_sky =false; _outline_mode=EDGE_DETECT_THIN; _edge_detect =EDGE_DETECT_NONE; _edge_soften =EDGE_SOFTEN_NONE; _fur_gravity =-1 ; _fur_vel_scale=-0.75f; _eye_dist =0.064f; _view_square_pixel =false; _view_main.fov_mode=FOV_Y; _view_fov = _view_main.fov.x = _view_main.fov.y =DegToRad(70); _view_main.from =_view_from=0.05f; _view_main.range =100; _view_main.full =true; // needed for 'viewReset' which will always set full viewport if last was full too _smaa_threshold=0.1f; } void Display::init() // make this as a method because if we put this to Display constructor, then 'SecondaryContexts' may not have been initialized yet { secondaryOpenGLContexts(1); // default 1 secondary context // re-use cached result obtained at app startup, because if the app is currently fullscreen at a custom resolution, then the monitor will return that resolution, however this function is used for getting default resolutions #if WINDOWS_OLD EnumDisplayMonitors(null, null, EnumMonitors, 0); // list all monitors at app startup so we can know their original sizes #elif WINDOWS_NEW IDXGIFactory1 *factory=null; CreateDXGIFactory1(__uuidof(IDXGIFactory1), (Ptr*)&factory); if(factory) { IDXGIAdapter *adapter=null; factory->EnumAdapters(0, &adapter); if(adapter) { for(Int i=0; ; i++) { IDXGIOutput *output=null; adapter->EnumOutputs(i, &output); if(output) { DXGI_OUTPUT_DESC desc; if(OK(output->GetDesc(&desc)))_monitors.get(desc.Monitor)->set(desc); output->Release(); }else break; } adapter->Release(); } factory->Release(); } #endif } /******************************************************************************/ void Display::del() { Gui.del(); // deleting Gui should be outside of '_lock' lock (because for example it can wait for a thread working in the background which is using '_lock') SyncLocker locker(_lock); _initialized=false; gamma(0); // reset gamma when closing app end(); VR.delImages(); ShutFont(); ShutVtxInd(); DisplayState::del(); Sh.del(); Renderer.del(); Images.del(); _modes.del(); #if DX9 RELEASE(Query ); RELEASE(D3D ); RELEASE(D3DBase); #elif DX11 if(SwapChain)SwapChain->SetFullscreenState(false, null); // full screen state must be first disabled, according to http://msdn.microsoft.com/en-us/library/windows/desktop/bb205075(v=vs.85).aspx#Destroying RELEASE(SwapChain); RELEASE(Output); RELEASE(Adapter); RELEASE(Factory); RELEASE(Query); RELEASE(D3DC1); RELEASE(D3DC); RELEASE(D3D); #elif GL if(FBO){glDeleteFramebuffers(1, &FBO); FBO=0;} if(VAO){glDeleteVertexArrays(1, &VAO); VAO=0;} SecondaryContexts.del(); MainContext .del(); #if WINDOWS if(hDC){ReleaseDC(App.Hwnd(), hDC); hDC=null;} SetDisplayMode(0); // switch back to the desktop #elif MAC [OpenGLContext release]; OpenGLContext=null; #elif LINUX SetDisplayMode(0); // switch back to the desktop if(vid_mode){XFree(vid_mode); vid_mode=null;} vid_modes=0; // free after 'SetDisplayMode' #elif ANDROID if(GLDisplay){eglTerminate(GLDisplay); GLDisplay=null;} #endif #endif } /******************************************************************************/ #if DX9 // codes used for detecting GPU VRAM #define DDENUM_ATTACHEDSECONDARYDEVICES 0x00000001L typedef BOOL (FAR PASCAL*LPDDENUMCALLBACKEXA)(GUID FAR *, LPSTR, LPSTR, LPVOID, HMONITOR); typedef HRESULT (WINAPI*LPDIRECTDRAWENUMERATEEXA)(LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags); typedef BOOL (WINAPI*PfnCoSetProxyBlanket)(IUnknown* pProxy, DWORD dwAuthnSvc, DWORD dwAuthzSvc, OLECHAR* pServerPrincName, DWORD dwAuthnLevel, DWORD dwImpLevel, RPC_AUTH_IDENTITY_HANDLE pAuthInfo, DWORD dwCapabilities); static CLSID CLSID_WbemLocator={0x4590F811, 0x1D3A, 0x11D0, {0x89, 0x1F, 0, 0xAA, 0, 0x4B, 0x2E, 0x24}}; static GUID IID_IWbemLocator={0xDC12A687, 0x737F, 0x11CF, {0x88, 0x4D, 0, 0xAA, 0, 0x4B, 0x2E, 0x24}}; struct Match { HMONITOR monitor; Char8 driver_name[512]; Bool found; Match(HMONITOR monitor) : monitor(monitor) {found=false; driver_name[0]=0;} }; static BOOL WINAPI DDEnumCallbackEx(GUID FAR *lpGUID, LPSTR lpDriverDescription, LPSTR lpDriverName, LPVOID lpContext, HMONITOR hm) { Match &match=*(Match*)lpContext; if(match.monitor==hm) { match.found=true; Set(match.driver_name, lpDriverName); return false; // stop enumerating } return true; // keep looking } static Bool GetDeviceIDFromHMonitor(HMONITOR hm, WCHAR *strDeviceID, int cchDeviceID) { DLL ddraw; if( ddraw.createFile(u"Ddraw.dll")) if(LPDIRECTDRAWENUMERATEEXA DirectDrawEnumerateEx=(LPDIRECTDRAWENUMERATEEXA)ddraw.getFunc("DirectDrawEnumerateExA")) { Match match(hm); DirectDrawEnumerateEx(DDEnumCallbackEx, &match, DDENUM_ATTACHEDSECONDARYDEVICES); if(match.found) { DISPLAY_DEVICEA dispdev; Zero(dispdev); dispdev.cb=SIZE(dispdev); for(Int i=0; EnumDisplayDevicesA(null, i, &dispdev, 0); i++) if(!(dispdev.StateFlags&DISPLAY_DEVICE_MIRRORING_DRIVER ) // skip devices that are monitors that echo another display && (dispdev.StateFlags&DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) // process only devices that are attached && Equal(match.driver_name, dispdev.DeviceName)) { MultiByteToWideChar(CP_ACP, 0, dispdev.DeviceID, -1, strDeviceID, cchDeviceID); return true; } } } return false; } static Long DeviceMemory(Int adapter_index) { Long size=-1; if(HMONITOR monitor=D3DBase->GetAdapterMonitor(adapter_index)) { WCHAR strInputDeviceID[1024]; if(GetDeviceIDFromHMonitor(monitor, strInputDeviceID, Elms(strInputDeviceID))) { IWbemLocator *pIWbemLocator=null; CoCreateInstance(CLSID_WbemLocator, null, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (Ptr*)&pIWbemLocator); if( pIWbemLocator) { // Using the locator, connect to WMI in the given namespace BSTR Namespace=SysAllocString(L"\\\\.\\root\\cimv2"); IWbemServices *pIWbemServices=null; pIWbemLocator->ConnectServer(Namespace, null, null, 0, 0, null, null, &pIWbemServices); if( pIWbemServices) { DLL ole; if( ole.createFile(u"Ole32.dll")) if(PfnCoSetProxyBlanket pfnCoSetProxyBlanket=(PfnCoSetProxyBlanket)ole.getFunc("CoSetProxyBlanket")) pfnCoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, null, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, null, 0); // Switch security level to IMPERSONATE BSTR Win32_VideoController=SysAllocString(L"Win32_VideoController"); IEnumWbemClassObject *pEnumVideoControllers=null; pIWbemServices->CreateInstanceEnum(Win32_VideoController, 0, null, &pEnumVideoControllers); if( pEnumVideoControllers) { BSTR PNPDeviceID=SysAllocString(L"PNPDeviceID"), AdapterRAM=SysAllocString(L"AdapterRAM"); IWbemClassObject *video_controllers[16]={0}; DWORD returned=0; Bool found=false; pEnumVideoControllers->Reset(); // Get the first one in the list if(OK(pEnumVideoControllers->Next(5000, Elms(video_controllers), video_controllers, &returned))) // 5 second timeout { FREP(returned)if(IWbemClassObject *controller=video_controllers[i]) { if(!found) { VARIANT var; if(OK(controller->Get(PNPDeviceID, 0, &var, null, null)))if(wcsstr(var.bstrVal, strInputDeviceID))found=true; VariantClear(&var); if(found) { if(OK(controller->Get(AdapterRAM, 0, &var, null, null)))size=var.ulVal; VariantClear(&var); } } controller->Release(); } } if(AdapterRAM )SysFreeString(AdapterRAM); if(PNPDeviceID)SysFreeString(PNPDeviceID); pEnumVideoControllers->Release(); } if(Win32_VideoController)SysFreeString(Win32_VideoController); pIWbemServices->Release(); } if(Namespace)SysFreeString(Namespace); pIWbemLocator->Release(); } } } return size; } #endif void Display::createDevice() { if(LogInit)LogN("Display.createDevice"); SyncLocker locker(_lock); #if DX9 if(!(D3DBase=Direct3DCreate9(D3D_SDK_VERSION)))Exit(MLTC(u"Direct3D not found.\nPlease install the newest DirectX.", PL,u"Direct3D nie odnaleziony.\nProszę zainstalować najnowszy DirectX.")); // get device description if(!deviceName().is()){D3DADAPTER_IDENTIFIER9 id; if(OK(D3DBase->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &id)))_device_name=id.Description;} // check required capabilites D3DCAPS9 caps; D3DBase->GetDeviceCaps(0, D3DDEVTYPE_HAL, &caps); if(caps.PrimitiveMiscCaps&D3DPMISCCAPS_NULLREFERENCE)Exit(MLTC(u"Video Card supporting minimum requirements not found.", PL,u"Nie odnaleziono karty graficznej spełniającej minimalne wymagania.")); Int vs_ver =D3DSHADER_VERSION_MAJOR(caps.VertexShaderVersion), ps_ver =D3DSHADER_VERSION_MAJOR(caps. PixelShaderVersion); _shader_model=((ps_ver>=3) ? SM_3 : SM_UNKNOWN); if(shaderModel()=ps_ver) // use hardware vertex processing only if HW T&L and VS ver matches PS ver (in HW) { #if 1 flag=D3DCREATE_MIXED_VERTEXPROCESSING; // this was tested and results in better performance for dynamic vertex buffers than D3DCREATE_HARDWARE_VERTEXPROCESSING, TODO: however https://msdn.microsoft.com/en-us/library/windows/desktop/bb172527(v=vs.85).aspx mentions that on Win10 this should be avoided, however tests are inconclusive, check again in the future using "Tests/Vertex Buffering.cpp" #else flag=D3DCREATE_HARDWARE_VERTEXPROCESSING; if(caps.DevCaps&D3DDEVCAPS_PUREDEVICE)flag|=D3DCREATE_PUREDEVICE; // this requires D3DCREATE_HARDWARE_VERTEXPROCESSING #endif } //if(App.flag&APP_DX_THREAD_SAFE)flag|=D3DCREATE_MULTITHREADED ; //if(App.flag&APP_DX_MANAGEMENT )flag|=D3DCREATE_DISABLE_DRIVER_MANAGEMENT_EX; flag|=D3DCREATE_FPU_PRESERVE ; // enumerate display modes MemtN modes; for(Int i=0; ; i++) { DEVMODE mode; Zero(mode); mode.dmSize=SIZE(mode); if(!EnumDisplaySettings(null, i, &mode))break; modes.include(VecI2(mode.dmPelsWidth, mode.dmPelsHeight)); } _modes=modes; _modes.sort(Compare); // init if(!findMode())Exit("Valid display mode not found."); if(!exclusive() && full()){if(!SetDisplayMode(2))Exit("Can't set fullscreen mode."); adjustWindow();} if(OK(D3DBase->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, App.Hwnd(), flag, &D3DPP, &D3D))) { _can_draw=true; _no_gpu =false; }else { _can_draw =false; _no_gpu =true; _shader_model=SM_3; if((App.flag&APP_ALLOW_NO_GPU) ? !OK(D3DBase->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_NULLREF, App.Hwnd(), flag, &D3DPP, &D3D)) : true)Exit(MLTC(u"Can't create Direct3D Device.", PL,u"Nie można utworzyć Direct3D.")); } if(D3D) { _device_mem=DeviceMemory(D3DADAPTER_DEFAULT); //if(_device_mem<0)_device_mem=D3D->GetAvailableTextureMem(); // this is the total memory, including system memory (for example 4GB is returned when having only 2GB GPU) D3D->CreateQuery(D3DQUERYTYPE_EVENT, &Query); } #elif DX11 UInt flags=(D3D_DEBUG ? D3D11_CREATE_DEVICE_DEBUG : 0); // DO NOT include D3D11_CREATE_DEVICE_SINGLETHREADED to allow multi-threaded resource creation - https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-render-multi-thread // ADAPTER = GPU // OUTPUT = MONITOR U64 adapter_id=VR._adapter_id; if( adapter_id) // if want a custom adapter { IDXGIFactory1 *factory=null; CreateDXGIFactory1(__uuidof(IDXGIFactory1), (Ptr*)&factory); if(factory) { for(Int i=0; OK(factory->EnumAdapters(i, &Adapter)); i++) { if(!Adapter)break; #if DEBUG IDXGIOutput *output=null; for(Int i=0; OK(Adapter->EnumOutputs(i, &output)); i++) { DXGI_OUTPUT_DESC desc; output->GetDesc(&desc); RELEASE(output); } #endif DXGI_ADAPTER_DESC desc; if(OK(Adapter->GetDesc(&desc))) { ASSERT(SIZE(desc.AdapterLuid)==SIZE(adapter_id)); if(EqualMem(&adapter_id, &desc.AdapterLuid, SIZE(adapter_id)))break; // if this is the adapter, then use it and don't look any more } RELEASE(Adapter); } RELEASE(factory); // release 'factory' because we need to obtain it from the D3D Device in case it will be different } } D3D_FEATURE_LEVEL *feature_level_force=null; #if FORCE_D3D9_3 D3D_FEATURE_LEVEL fl=D3D_FEATURE_LEVEL_9_3 ; feature_level_force=&fl; #elif FORCE_D3D10_0 D3D_FEATURE_LEVEL fl=D3D_FEATURE_LEVEL_10_0; feature_level_force=&fl; #elif FORCE_D3D10_1 D3D_FEATURE_LEVEL fl=D3D_FEATURE_LEVEL_10_1; feature_level_force=&fl; #endif if(OK(D3D11CreateDevice(Adapter, Adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, null, flags, feature_level_force, feature_level_force ? 1 : 0, D3D11_SDK_VERSION, &D3D, &FeatureLevel, &D3DC))) { _can_draw=true; _no_gpu =false; if(FeatureLevelSetExceptionMode(D3D11_RAISE_FLAG_DRIVER_INTERNAL_ERROR); D3DC->QueryInterface(__uuidof(ID3D11DeviceContext1), (Ptr*)&D3DC1); _shader_model=((FeatureLevel>=D3D_FEATURE_LEVEL_11_0) ? SM_5 : (FeatureLevel>=D3D_FEATURE_LEVEL_10_1) ? SM_4_1 : SM_4); IDXGIDevice1 *device=null; D3D->QueryInterface(__uuidof(IDXGIDevice1), (Ptr*)&device); if(device) { device->SetMaximumFrameLatency(1); // set max frame latency, for WINDOWS_OLD this doesn't seem to have any effect, however for WINDOWS_NEW it makes a big difference (it makes it work as WINDOWS_OLD), this may be related to the type of SwapChain, so always call this just in case, as without this, the latency is very slow (for example drawing something at mouse position and moving the mouse in circles) if(!Adapter)device->GetAdapter(&Adapter); // if adapter is unknown RELEASE(device); } if(!Adapter) // if adapter is unknown { IDXGIDevice *device=null; D3D->QueryInterface(__uuidof(IDXGIDevice), (Ptr*)&device); if(device) { device->GetAdapter(&Adapter); RELEASE(device); } } if(!Factory) // if Factory is unknown { if(Adapter)Adapter->GetParent(WINDOWS_OLD ? __uuidof(IDXGIFactory1) : __uuidof(IDXGIFactory2), (Ptr*)&Factory); if(!Factory)Exit("Can't access DXGIFactory.\nPlease install latest DirectX and Graphics Drivers."); } IDXGIFactory5 *factory5=null; Factory->QueryInterface(__uuidof(IDXGIFactory5), (Ptr*)&factory5); if(factory5) { int allow_tearing=false; // must be 'int' because 'bool' will fail if(OK(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing, SIZE(allow_tearing))))AllowTearing=(allow_tearing!=0); factory5->Release(); } if(Adapter) { DXGI_ADAPTER_DESC desc; if(OK(Adapter->GetDesc(&desc))) { if(!deviceName().is())_device_name=desc.Description; _device_mem=desc.DedicatedVideoMemory; } MemtN modes; // store display modes for all outputs, in case user prefers to use another monitor rather than the main display IDXGIOutput *output=null; for(Int i=0; OK(Adapter->EnumOutputs(i, &output)); i++) // first output is always the main display { DXGI_FORMAT mode=DXGI_FORMAT_R8G8B8A8_UNORM; // always use this mode in case system doesn't support 10-bit color UInt descs_elms=0; output->GetDisplayModeList(mode, 0, &descs_elms, null); // get number of mode descs MemtN descs; descs.setNum(descs_elms ); output->GetDisplayModeList(mode, 0, &descs_elms, descs.data()); // get mode descs FREPA(descs)modes.binaryInclude(VecI2(descs[i].Width, descs[i].Height), Compare); // add from the start to avoid unnecessary memory moves RELEASE(output); } _modes=modes; } // init if(!findMode())Exit("Valid display mode not found."); #if WINDOWS_OLD if(!exclusive() && full()){if(!SetDisplayMode(2))Exit("Can't set fullscreen mode."); adjustWindow();} again: Factory->CreateSwapChain(D3D, &SwapChainDesc, &SwapChain); if(!SwapChain && SwapChainDesc.BufferDesc.Format==DXGI_FORMAT_R10G10B10A2_UNORM){SwapChainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; goto again;} // if failed with 10-bit then try again with 8-bit Factory->MakeWindowAssociation(App.Hwnd(), DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_WINDOW_CHANGES|DXGI_MWA_NO_PRINT_SCREEN); // this needs to be called after 'CreateSwapChain' #else again: Factory->CreateSwapChainForCoreWindow(D3D, (IUnknown*)App._hwnd, &SwapChainDesc, null, &SwapChain); if(!SwapChain && SwapChainDesc.Format==DXGI_FORMAT_R10G10B10A2_UNORM){SwapChainDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; goto again;} // if failed with 10-bit then try again with 8-bit #endif if(!SwapChain)Exit("Can't create Direct3D Swap Chain."); //if( SwapChain && Output && !SwapChainDesc.Windowed)SwapChain->SetFullscreenState(true, Output); // if we want a custom output then we need to apply it now, otherwise the fullscreen can occur on the main display D3D11_QUERY_DESC query_desc={D3D11_QUERY_EVENT, 0}; D3D->CreateQuery(&query_desc, &Query); #elif GL _shader_model=(GL_ES ? SM_GL_ES_2 : SM_GL); if(FlagTest(App.flag, APP_ALLOW_NO_GPU)) // completely disable hardware on OpenGL because there's no way to know if it's available { _can_draw=false; _no_gpu =true; }else { _can_draw=true; _no_gpu =false; } #if WINDOWS PIXELFORMATDESCRIPTOR pfd= { SIZE(PIXELFORMATDESCRIPTOR), // size of 'pfd' 1, // version number PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32, // color bits 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accumulation bits ignored 24, // 24-bit depth buffer 8, // 8-bit stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main drawing layer 0, // reserved 0, 0, 0 // layer masks ignored }; Int PixelFormat; if(!(hDC = GetDC(App.Hwnd() )))Exit("Can't create an OpenGL Device Context."); if(!(PixelFormat =ChoosePixelFormat(hDC, &pfd)))Exit("Can't find a suitable PixelFormat."); if(!( SetPixelFormat(hDC, PixelFormat, &pfd)))Exit("Can't set the PixelFormat."); if(!(MainContext.context= wglCreateContext(hDC )))Exit("Can't create an OpenGL Context."); MainContext.lock(); if(glewInit()!=GLEW_OK || !GLEW_VERSION_3_2)Exit("OpenGL 3.2 support not available.\nGraphics Driver not installed or better video card is required."); // 3.2 needed for 'glDrawElementsBaseVertex', 3.1 needed for instancing, 3.0 needed for 'glColorMaski', 'gl_ClipDistance', 'glClearBufferfv', 'glGenVertexArrays', 'glMapBufferRange', otherwise 2.0 is good enough glewSafe(); if(wglCreateContextAttribsARB) { const int attribs[]={WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 2, 0}; if(HGLRC context=wglCreateContextAttribsARB(hDC, 0, attribs)) { MainContext.del(); MainContext.context=context; MainContext.lock(); } } // enumerate display modes MemtN modes; for(Int i=0; ; i++) { DEVMODE mode; Zero(mode); mode.dmSize=SIZE(mode); if(!EnumDisplaySettings(null, i, &mode))break; modes.include(VecI2(mode.dmPelsWidth, mode.dmPelsHeight)); } _modes=modes; _modes.sort(Compare); #elif MAC const CGLPixelFormatAttribute profile_versions[]= { (CGLPixelFormatAttribute)kCGLOGLPVersion_Legacy , // NSOpenGLProfileVersionLegacy (CGLPixelFormatAttribute)kCGLOGLPVersion_GL3_Core, // NSOpenGLProfileVersion3_2Core (CGLPixelFormatAttribute)kCGLOGLPVersion_GL4_Core, // NSOpenGLProfileVersion4_1Core }; CGLPixelFormatObj pf=null; REPD (hw , 2) // HW acceleration, most important !! it's very important to check it first, in case device supports only 3.2 accelerated, and 4.1 perhaps could be done in software (if that's possible, it's very likely as one user with Intel HD 3300 which has 3.3 GL, reported poor performance without this) so first we check all profiles looking for accelerated, and if none are found, then try software !! REPAD(ver, profile_versions) // profile version REPD (buf, 2) { const CGLPixelFormatAttribute attribs[]= { buf ? kCGLPFATripleBuffer : kCGLPFADoubleBuffer, // triple/double buffered kCGLPFADepthSize , (CGLPixelFormatAttribute)24, // depth buffer kCGLPFAStencilSize, (CGLPixelFormatAttribute) 8, // stencil kCGLPFAOpenGLProfile, profile_versions[ver], // version hw ? kCGLPFAAccelerated : kCGLPFAAllowOfflineRenderers, // HW/Soft (CGLPixelFormatAttribute)0, // end of list }; GLint num_pixel_formats; CGLChoosePixelFormat(attribs, &pf, &num_pixel_formats); if(pf)goto found_pf; } Exit("Can't create an OpenGL Pixel Format."); found_pf: CGLCreateContext(pf, null, &MainContext.context); CGLDestroyPixelFormat(pf); if(!MainContext.context)Exit("Can't create an OpenGL Context."); if(MAC_GL_MT)Bool mt_ok=(CGLEnable(MainContext.context, kCGLCEMPEngine)!=kCGLNoError); MainContext.lock(); OpenGLContext=[[NSOpenGLContext alloc] initWithCGLContextObj:MainContext.context]; [OpenGLContext setView:OpenGLView]; [App.Hwnd() makeKeyAndOrderFront:NSApp]; // show only after everything finished (including GL context to avoid any flickering) // enumerate display modes CGDirectDisplayID display[256]; CGDisplayCount displays=0; MemtN modes; if(!CGGetActiveDisplayList(Elms(display), display, &displays))REP(displays) if(CFArrayRef display_modes=CGDisplayCopyAllDisplayModes(display[i], null)) { REP(CFArrayGetCount(display_modes)) { CGDisplayModeRef mode=(CGDisplayModeRef)CFArrayGetValueAtIndex(display_modes, i); UInt flags=CGDisplayModeGetIOFlags(mode); Bool ok =FlagTest(flags, kDisplayModeSafetyFlags); if( ok)modes.include(VecI2(CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode))); } CFRelease(display_modes); } _modes=modes; _modes.sort(Compare); #elif LINUX if(XDisplay) { #if 0 // 2.0 context if(!(MainContext.context=glXCreateNewContext(XDisplay, GLConfig, GLX_RGBA_TYPE, null, true)))Exit("Can't create a OpenGL Context."); #else // 3.0+ context (this does not link on some old graphics drivers when compiling, "undefined reference to glXCreateContextAttribsARB", it would need to be accessed using 'glXGetProcAddress') // access 'glXCreateContextAttribsARB', on Linux we don't need an existing GL context to be able to load extensions via 'glXGetProcAddressARB' typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC) (::Display* dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB=(PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((C GLubyte*)"glXCreateContextAttribsARB"); const int attribs[]= { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 2, 0, }; // create context if(!glXCreateContextAttribsARB || !(MainContext.context=glXCreateContextAttribsARB(XDisplay, GLConfig, null, true, attribs)))Exit("Can't create a OpenGL 3.2 Context."); #endif XSync(XDisplay, false); // Forcibly wait on any resulting X errors MainContext.lock(); glXSwapInterval=(glXSwapIntervalType)glXGetProcAddressARB((C GLubyte*)"glXSwapIntervalEXT"); // access it via 'glXGetProcAddressARB' because some people have linker errors "undefined reference to 'glXSwapIntervalEXT' // get available modes MemtN modes; if(XF86VidModeGetAllModeLines(XDisplay, DefaultScreen(XDisplay), &vid_modes, &vid_mode))for(int i=0; i=18) // proceed only if we're trying GLES 2.0, or 3.0 AND AndroidAPI>=18 (which is the Android Version which started supporting 3.0), this is because Asus Transformer Prime TF201 succeeds with 3.0 context but it doesn't actually support it (TF201 has Android 4.1.1 which is API 16) { EGLint ctx_attribs[]= { EGL_CONTEXT_CLIENT_VERSION, (gl_ver==0) ? 3 : 2, // try OpenGL ES 3.0 context first, then fallback to 2.0 EGL_NONE, }; FREPD(c, 2) // colors (process this as 1st in loop as it's most important) FREPD(d, 3) // depth (process this as 2nd in loop as it's more important) FREPD(s, 2) // stencil (process this as 3rd in loop as it's less important) FREPD(a, 2) // alpha (process this as 4th in loop as it's least important) { has_alpha=(c==0 && a==0); bit16 =(c==1); ds_type =((d==0) ? ((s==0) ? IMAGE_D24S8 : IMAGE_D24X8) : (d==1) ? IMAGE_D32 : IMAGE_D16); EGLint attribs[]= { EGL_SURFACE_TYPE , EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_BLUE_SIZE , (c==0) ? 8 : 5, EGL_GREEN_SIZE , (c==0) ? 8 : 6, EGL_RED_SIZE , (c==0) ? 8 : 5, EGL_ALPHA_SIZE , has_alpha ? 8 : 0, EGL_DEPTH_SIZE , (d==0) ? 24 : (d==1) ? 32 : 16, EGL_STENCIL_SIZE , (s==0) ? 8 : 0, EGL_NONE }; if(LogInit)LogN(S+"Trying config GL:"+gl_ver+", C:"+c+", D:"+d+", S:"+s+", A:"+a); EGLint num_configs=0; if(eglChooseConfig(GLDisplay, attribs, &GLConfig, 1, &num_configs)==EGL_TRUE) if(num_configs>=1) { EGLint format; eglGetConfigAttrib(GLDisplay, GLConfig, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(AndroidApp->window, 0, 0, format); if(MainContext.surface=eglCreateWindowSurface(GLDisplay, GLConfig, AndroidApp->window, null)) { if(MainContext.context=eglCreateContext(GLDisplay, GLConfig, null, ctx_attribs)) { if(gl_ver==0)_shader_model=SM_GL_ES_3; // we succeeded with creating a 3.0 context goto context_ok; } MainContext.del(); } } } } Exit("Can't create an OpenGL Context."); context_ok: MainContext.lock(); if(LogInit)LogN("EGL OK"); EGLint width, height; eglQuerySurface(GLDisplay, MainContext.surface, EGL_WIDTH , &width ); eglQuerySurface(GLDisplay, MainContext.surface, EGL_HEIGHT, &height); Renderer._main .forceInfo(width, height, 1, bit16 ? IMAGE_B5G6R5 : has_alpha ? IMAGE_R8G8B8A8 : IMAGE_R8G8B8X8, IMAGE_SURF)._samples=samples; Renderer._main_ds.forceInfo(width, height, 1, ds_type , IMAGE_DS )._samples=samples; if(LogInit)LogN(S+"Renderer._main: "+Renderer._main.w()+'x'+Renderer._main.h()+", type: "+ImageTI[Renderer._main.hwType()].name+", ds_type: "+ImageTI[Renderer._main_ds.hwType()].name); #elif IOS if(MainContext.context=[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3])_shader_model=SM_GL_ES_3;else if(MainContext.context=[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]) { _shader_model=SM_GL_ES_2; // iOS has a bug in which it falsely returns success for creating ETC formats on OpenGL ES 2 even though they're not supported, so as a workaround, disable them completely ConstCast(ImageTI[IMAGE_ETC1 ].format)=0; ConstCast(ImageTI[IMAGE_ETC2 ].format)=0; ConstCast(ImageTI[IMAGE_ETC2_A1].format)=0; ConstCast(ImageTI[IMAGE_ETC2_A8].format)=0; }else Exit("Can't create a OpenGL ES 2.0 Context."); MainContext.context.multiThreaded=false; // disable multi-threaded rendering as enabled actually made things slower, TOOD: check again in the future !! if enabling then probably all contexts have to be enabled as well, secondary too, because VAO from VBO's on a secondary thread could fail, as in Dungeon Hero, needs checking !! MainContext.lock(); #elif WEB EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs); attrs.minorVersion=0; attrs.alpha =false; // this would enable compositing graphics using transparency onto the web page attrs.depth =true; attrs.stencil =true; attrs.antialias =false; attrs.preserveDrawingBuffer=false; attrs.enableExtensionsByDefault=true; attrs.preferLowPowerToHighPerformance=false; for(Int gl_ver=2; gl_ver>=1; gl_ver--) // start from WebGL 2.0 (ES3) down to 1.0 (ES2) { attrs.majorVersion=gl_ver; if(MainContext.context=emscripten_webgl_create_context(null, &attrs)) { _shader_model=((gl_ver>=2) ? SM_GL_ES_3 : SM_GL_ES_2); goto context_ok; } } Exit("Can't create an OpenGL Context."); context_ok: MainContext.lock(); Byte samples=(attrs.antialias ? 4 : 1); int width, height; emscripten_get_canvas_element_size(null, &width, &height); Renderer._main .forceInfo(width, height, 1, IMAGE_R8G8B8A8 , IMAGE_SURF)._samples=samples; Renderer._main_ds.forceInfo(width, height, 1, attrs.stencil ? IMAGE_D24S8 : IMAGE_D24X8, IMAGE_DS )._samples=samples; #endif if(!deviceName().is()) { _device_name=(CChar8*)glGetString(GL_RENDERER); #if LINUX _device_name.removeOuterWhiteChars(); // Linux may have unnecessary spaces at the end #endif } if(LogInit)LogN("Secondary Contexts"); if(SecondaryContexts.elms()) { REPA(SecondaryContexts)if(!SecondaryContexts[i].createSecondary()) { LogN(S+"Failed to create a Secondary OpenGL Context" #if ANDROID +", error:"+eglGetError() #endif ); SecondaryContexts.remove(i); // remove after error code was displayed } MainContext.lock(); // lock main context because secondary were locked during creation to set some things } if(LogInit)LogN("Secondary Contexts OK"); if(D.shaderModelGLES2()) // GLES2 requires internalFormat to be as follows, without it WebGL1 will fail - https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml { #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE_ALPHA 0x190A ConstCast(ImageTI[IMAGE_R8G8B8A8].format)=GL_RGBA; ConstCast(ImageTI[IMAGE_R8G8B8 ].format)=GL_RGB; ConstCast(ImageTI[IMAGE_L8A8 ].format)=GL_LUMINANCE_ALPHA; ConstCast(ImageTI[IMAGE_L8 ].format)=GL_LUMINANCE; ConstCast(ImageTI[IMAGE_A8 ].format)=GL_ALPHA; ConstCast(ImageTI[IMAGE_F32_4 ].format)=GL_RGBA; ConstCast(ImageTI[IMAGE_F32_3 ].format)=GL_RGB; ConstCast(ImageTI[IMAGE_F16_4 ].format)=GL_RGBA; ConstCast(ImageTI[IMAGE_F16_3 ].format)=GL_RGB; } if(LogInit) { LogN(S+"Device Name: " +_device_name); LogN(S+"Device Vendor: " +(CChar8*)glGetString(GL_VENDOR )); LogN(S+"Device Version: " +(CChar8*)glGetString(GL_VERSION )); LogN(S+"Device Extensions: "+(CChar8*)glGetString(GL_EXTENSIONS)); #if GL_ES int i; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS , &i); LogN(S+"GL_MAX_VERTEX_UNIFORM_VECTORS: "+i); glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &i); LogN(S+"GL_MAX_FRAGMENT_UNIFORM_VECTORS: "+i); #endif } if(!findMode())Exit("Valid display mode not found."); setSync(); if(LogInit)LogN("FBO"); glGenFramebuffers(1, &FBO); if(!FBO)Exit("Couldn't create OpenGL Frame Buffer Object (FBO)"); if(D.notShaderModelGLES2()){glGenVertexArrays(1, &VAO); if(!VAO)Exit("Couldn't create OpenGL Vertex Arrays (VAO)");} #if WINDOWS if(full()){if(!SetDisplayMode(2))Exit("Can't set fullscreen mode."); adjustWindow();} #elif MAC if(!SetDisplayMode(2))Exit("Can't set display mode."); #elif LINUX if(full()){if(!SetDisplayMode(2))Exit("Can't set display mode."); adjustWindow();} // 'adjustWindow' because we need to set fullscreen state #elif IOS fbo(FBO); // set custom frame buffer, on iOS there's only one FBO and one FBO change, and it is here, this is because there's no default(0) fbo on this platform #endif // call these as soon as possible because they affect all images (including those created in the renderer) glPixelStorei(GL_PACK_ALIGNMENT , 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); #endif #if MOBILE T._modes.setNum(2); T._modes[1]=T._modes[0]=screen(); T._modes[1].swap(); #endif } void Display::androidClose() { #if ANDROID SyncLocker locker(_lock); if(GLDisplay) { MainContext.unlock(); if(MainContext.surface)eglDestroySurface(GLDisplay, MainContext.surface); } MainContext.surface=null; #endif } void Display::androidOpen() { #if ANDROID SyncLocker locker(_lock); androidClose(); if(GLDisplay && MainContext.context) { EGLint format; eglGetConfigAttrib(GLDisplay, GLConfig, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(AndroidApp->window, 0, 0, format); MainContext.surface=eglCreateWindowSurface(GLDisplay, GLConfig, AndroidApp->window, null); if(!MainContext.surface)Exit("Can't create EGLSurface."); MainContext.lock(); }else Exit("OpenGL Display and MainContext not available."); #endif } Bool Display::create() { if(LogInit)LogN("Display.create"); createDevice(); getGamma(); getCaps(); Sh.createSamplers(); DisplayState::create(); setDeviceSettings(); Sh.create(); InitMatrix(); // !! call this after creating main shaders, because it creates the "ObjMatrix, ObjVel" shader buffers !! if(!Renderer.rtCreate())Exit("Can't create Render Targets."); // !! call this after creating shaders because it modifies shader values !! InitVtxInd(); Renderer.create(); colorPalette(ImagePtr().get("Img/color palette.img")); VR.createImages(); // !! call this before 'after', because VR gui image may affect aspect ratio of display !! after(false); begin(); Gui.create(); // set default settings {Byte v=texFilter (); _tex_filter ^=1 ; texFilter (v);} {Bool v=texMipFilter(); _tex_mip_filter^=1 ; texMipFilter(v);} {Bool v=bloomMaximum(); _bloom_max =false ; bloomMaximum(v);} // resetting will load shaders {auto v=edgeSoften (); _edge_soften =EDGE_SOFTEN_NONE; edgeSoften (v);} // resetting will load shaders {auto v=edgeDetect (); _edge_detect =EDGE_DETECT_NONE; edgeDetect (v);} // resetting will load shaders {Flt v=grassRange (); _grass_range =-1 ; grassRange (v);} lod (_lod_factor, _lod_factor_shadow, _lod_factor_mirror); shadowJitterSet (); MotionScaleChanged(); SetMatrix (); _initialized=true; return true; } /******************************************************************************/ Bool Display::created() { #if DX9 return D3D!=null; #elif DX11 return D3DC!=null; #elif GL return MainContext.is(); #endif } /******************************************************************************/ void ThreadMayUseGPUData() { #if GL && HAS_THREADS Ptr context=GetCurrentContext(); if(!context) { ContextLock.on(); for(;;) { REPA(SecondaryContexts)if(!SecondaryContexts[i].locked) { SecondaryContexts[i].lock(); goto context_locked; } if(!SecondaryContexts.elms())Exit("No secondary OpenGL contexts have been created"); ContextLock.off(); ContextUnlocked.wait(); // wait until any other context is unlocked ContextLock.on (); } context_locked: ContextLock.off(); } #endif } void ThreadFinishedUsingGPUData() { #if GL && HAS_THREADS if(Ptr context=GetCurrentContext()) { ContextLock.on(); REPA(SecondaryContexts)if(SecondaryContexts[i].context==context) { SecondaryContexts[i].unlock(); goto context_unlocked; } context_unlocked: ContextLock.off(); ContextUnlocked++; // notify of unlocking } #endif } /******************************************************************************/ static Int DisplaySamples(Int samples) { Clamp(samples, 1, 16); if(Renderer.anyDeferred() && D.deferredMSUnavailable())samples=1; #if DX9 if(D3DBase) { D3DFORMAT disp_format=D3DFMT_A8R8G8B8; if(!OK(D3DBase->CheckDeviceType(0, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, disp_format, D3DPP.Windowed)))disp_format=D3DFMT_X8R8G8B8; for(; samples>1; samples--) { DWORD col_levels=0, ds_levels=0; if(samples<=D3DMULTISAMPLE_16_SAMPLES) // there's no higher sample level on DX9 { DWORD levels=0; if(OK(D3DBase->CheckDeviceMultiSampleType(0, D3DDEVTYPE_HAL, disp_format , D3DPP.Windowed, D3DMULTISAMPLE_TYPE(samples), &levels)))MAX(col_levels, levels); if(OK(D3DBase->CheckDeviceMultiSampleType(0, D3DDEVTYPE_HAL, D3DFMT_D24S8, D3DPP.Windowed, D3DMULTISAMPLE_TYPE(samples), &levels)))MAX( ds_levels, levels); if(OK(D3DBase->CheckDeviceMultiSampleType(0, D3DDEVTYPE_HAL, D3DFMT_D24X8, D3DPP.Windowed, D3DMULTISAMPLE_TYPE(samples), &levels)))MAX( ds_levels, levels); } if(col_levels && ds_levels)break; // if there are some quality levels, then accept this multi-sampling } } #elif DX11 if(samples>1)samples=4; // only 4 samples are supported in DX10+ implementation #else samples=1; // other implementations don't support multi-sampling #endif return samples; } Bool Display::findMode() { SyncLocker locker(_lock); #if WINDOWS_NEW // on WindowsNew we can't change mode here, we need to set according to what we've got, instead 'RequestDisplayMode' can be called _res =WindowSize(true); _full=App.Fullscreen(); #elif IOS // '_res' will be set in 'mapMain' #elif MOBILE || WEB // Renderer._main is already available _res=Renderer._main.size(); #else RectI full, work; VecI2 max_normal_win_client_size, maximized_win_client_size; curMonitor(full, work, max_normal_win_client_size, maximized_win_client_size); #if FORCE_MAIN_DISPLAY if(resW()>=D.screenW() && resH()>=D.screenH())_full=true; #else if(resW()>=full.w() && resH()>=full.h())_full=true; // force fullscreen only if both dimensions are equal-bigger because on Windows it's perfectly fine to have a window as wide as the whole desktop #endif if(D.full()) { Int nearest=-1; Int desired_area=res().mul(), area_error; REPA(_modes) { C VecI2 &mode=_modes[i]; if(mode==res()){nearest=i; break;} // exact match Int ae=Abs(mode.mul()-desired_area); if(nearest<0 || ae=Min(maximized_win_client_size.x, max_normal_win_client_size.x+1) && resH()>=Min(maximized_win_client_size.y, max_normal_win_client_size.y+1))_res=maximized_win_client_size;/*else { don't limit for 2 reasons: 1) having multiple monitors we can make the window cover multiple monitors 2) on Windows initially we can drag the window only until 'max_normal_win_client_size', however after that we can drag again to make it bigger, and this time it will succeed, which means that making windows bigger than 'max_normal_win_client_size' is actually possible, unless it's a bug in the OS MIN(_res.x, max_normal_win_client_size.x); MIN(_res.y, max_normal_win_client_size.y); }*/ } #endif #if DX9 Zero(D3DPP); D3DPP.Windowed =(!exclusive() || !T.full()); // !! set this first !! D3DPP.BackBufferCount =(sync() ? 2 : 1); D3DPP.BackBufferWidth =resW(); D3DPP.BackBufferHeight =resH(); D3DPP.BackBufferFormat =D3DFMT_A8R8G8B8; if(D3DBase && !OK(D3DBase->CheckDeviceType(0, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, D3DPP.BackBufferFormat, D3DPP.Windowed)))D3DPP.BackBufferFormat=D3DFMT_X8R8G8B8; D3DPP.EnableAutoDepthStencil =false; D3DPP.hDeviceWindow =App.Hwnd(); D3DPP.SwapEffect =D3DSWAPEFFECT_DISCARD; D3DPP.MultiSampleQuality =0; D3DPP.MultiSampleType =D3DMULTISAMPLE_NONE; D3DPP.PresentationInterval =( sync() ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE); D3DPP.FullScreen_RefreshRateInHz=((_freq_want && !D3DPP.Windowed) ? _freq_want : D3DPRESENT_RATE_DEFAULT ); // set custom frequency only if desired and in true full-screen #elif DX11 Zero(SwapChainDesc); Bool sync=ActualSync(); #if WINDOWS_OLD SwapChainDesc.OutputWindow =App.Hwnd(); SwapChainDesc.Windowed =(!exclusive() || !T.full()); SwapChainDesc.BufferCount =(sync ? 3 : 2); // if we're rendering to VR display, then it has its own swap chain, and it handles the most intense rendering, so we don't need to have more buffers here, so keep it low to reduce memory usage SwapChainDesc.BufferDesc.Width =resW(); SwapChainDesc.BufferDesc.Height =resH(); SwapChainDesc.BufferDesc.Format =(GDI_COMPATIBLE ? DXGI_FORMAT_B8G8R8A8_UNORM : highMonitorPrecision() ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM); SwapChainDesc.SampleDesc.Count =1; SwapChainDesc.SampleDesc.Quality=0; SwapChainDesc.BufferUsage =DXGI_USAGE_RENDER_TARGET_OUTPUT|DXGI_USAGE_SHADER_INPUT|DXGI_USAGE_BACK_BUFFER; SwapChainDesc.Flags =DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH|(GDI_COMPATIBLE ? DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE : 0); if(_freq_want && !SwapChainDesc.Windowed) // set custom frequency only if desired and in true full-screen { SwapChainDesc.BufferDesc.RefreshRate.Numerator =_freq_want; SwapChainDesc.BufferDesc.RefreshRate.Denominator=1; } SwapChainDesc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD; #if !GDI_COMPATIBLE #if 0 /* disable this as last time this was tested, it provided slower results, also there was a few second slow down when changing sync during app runtime: windowed sync=false | windowed sync=true | fullscreen sync=true DXGI_SWAP_EFFECT_DISCARD 850 fps | 42 fps | 16.9 fps DXGI_SWAP_EFFECT_FLIP_DISCARD 655 fps | 34 fps | 16.4 fps */ { VecI4 ver=OSVerNumber(); if( ver.x>=10 )SwapChainDesc.SwapEffect=DXGI_SWAP_EFFECT_FLIP_DISCARD ;else // DXGI_SWAP_EFFECT_FLIP_DISCARD is available on Windows 10 if(Compare(ver, VecI4(6, 2, 0, 0))>=0)SwapChainDesc.SwapEffect=DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL is available on Windows 8 - https://msdn.microsoft.com/en-us/library/windows/desktop/bb173077(v=vs.85).aspx } #endif #endif if(AllowTearing && SwapChainDesc.SwapEffect==DXGI_SWAP_EFFECT_FLIP_DISCARD)SwapChainDesc.Flags|=DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; PresentFlags=((!sync && (SwapChainDesc.Flags&DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) && SwapChainDesc.Windowed) ? DXGI_PRESENT_ALLOW_TEARING : 0); // according to docs, we can use DXGI_PRESENT_ALLOW_TEARING only with DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING and in windowed mode - https://msdn.microsoft.com/en-us/library/windows/desktop/bb509554(v=vs.85).aspx SwapChainDesc.BufferDesc.Scaling=DXGI_MODE_SCALING_UNSPECIFIED; // can't always use 'DXGI_MODE_SCALING_STRETCHED' because it will fail to set highest supported resolution by the monitor, because that resolution never supports streching, so we set DXGI_MODE_SCALING_UNSPECIFIED which means it will be set according to the driver settings if(1 // find the monitor/output that we're going to use, and iterate all of its modes to check if that mode supports stretched mode && !SwapChainDesc.Windowed) // needed for exclusive fullscreen only and only when the driver has scaling set to DXGI_MODE_SCALING_CENTERED { IDXGIOutput *output; Bool ok=false; if(SwapChain) // if we already have a swap chain, then reuse its output { output=null; SwapChain->GetContainingOutput(&output); if(output){ok=true; goto has_output;} // set 'ok' so break will be called } if(Adapter) if(HMONITOR monitor=MonitorFromWindow(App.Hwnd(), MONITOR_DEFAULTTONEAREST)) // get nearest monitor for(Int i=0; ; i++) // iterate all outputs { output=null; Adapter->EnumOutputs(i, &output); if(output) { DXGI_OUTPUT_DESC desc; ok=(OK(output->GetDesc(&desc)) && desc.Monitor==monitor); // if found the monitor that we're going to use if(ok) { has_output: DXGI_FORMAT mode=DXGI_FORMAT_R8G8B8A8_UNORM; // always use this mode in case system doesn't support 10-bit color UInt descs_elms=0; output->GetDisplayModeList(mode, 0, &descs_elms, null); // get number of mode descs MemtN descs; descs.setNum(descs_elms ); output->GetDisplayModeList(mode, 0, &descs_elms, descs.data()); // get mode descs FREPA(descs) { C DXGI_MODE_DESC &mode=descs[i]; if(mode.Width==resW() && mode.Height==resH() && mode.Scaling!=DXGI_MODE_SCALING_UNSPECIFIED) // can't just check for ==DXGI_MODE_SCALING_STRETCHED because it's never listed, however DXGI_MODE_SCALING_CENTERED will be listed for modes that support stretching, so we use !=DXGI_MODE_SCALING_UNSPECIFIED to support both DXGI_MODE_SCALING_STRETCHED and DXGI_MODE_SCALING_CENTERED { SwapChainDesc.BufferDesc.Scaling=DXGI_MODE_SCALING_STRETCHED; break; } } } output->Release(); if(ok)break; }else break; } } #else // WINDOWS_NEW SwapChainDesc.Width =resW(); SwapChainDesc.Height=resH(); SwapChainDesc.Format=(GDI_COMPATIBLE ? DXGI_FORMAT_B8G8R8A8_UNORM : highMonitorPrecision() ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM); SwapChainDesc.Stereo=false; SwapChainDesc.SampleDesc.Count =1; SwapChainDesc.SampleDesc.Quality=0; SwapChainDesc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT|DXGI_USAGE_SHADER_INPUT|DXGI_USAGE_BACK_BUFFER; SwapChainDesc.BufferCount=(sync ? 3 : 2); // if we're rendering to VR display, then it has its own swap chain, and it handles the most intense rendering, so we don't need to have more buffers here, so keep it low to reduce memory usage SwapChainDesc.Scaling =DXGI_SCALING_STRETCH; SwapChainDesc.SwapEffect =DXGI_SWAP_EFFECT_FLIP_DISCARD; SwapChainDesc.AlphaMode =DXGI_ALPHA_MODE_IGNORE; SwapChainDesc.Flags =0; if(AllowTearing && SwapChainDesc.SwapEffect==DXGI_SWAP_EFFECT_FLIP_DISCARD)SwapChainDesc.Flags|=DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; PresentFlags=((!sync && (SwapChainDesc.Flags&DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)/* && SwapChainDesc.Windowed*/) ? DXGI_PRESENT_ALLOW_TEARING : 0); // according to docs, we can use DXGI_PRESENT_ALLOW_TEARING only with DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING and in windowed mode, in UWP there's no windowed check because "UWP apps that enter fullscreen mode by calling Windows::UI::ViewManagement::ApplicationView::TryEnterFullscreen() are fullscreen borderless windows and may use the flag" - https://msdn.microsoft.com/en-us/library/windows/desktop/bb509554(v=vs.85).aspx #endif #endif densityUpdate(); return true; } /******************************************************************************/ CChar8* Display::AsText(RESET_RESULT result) { switch(result) { case RESET_OK : return "RESET_OK"; case RESET_DEVICE_NOT_CREATED : return "RESET_DEVICE_NOT_CREATED"; case RESET_CUSTOM_LOST_FAILED : return "RESET_CUSTOM_LOST_FAILED"; case RESET_CUSTOM_RESET_FAILED : return "RESET_CUSTOM_RESET_FAILED"; case RESET_DEVICE_RESET_FAILED : return "RESET_DEVICE_RESET_FAILED"; case RESET_RENDER_TARGET_FAILED: return "RESET_RENDER_TARGET_FAILED"; default : return "RESET_UNKNOWN"; } } void Display::ResetFailed(RESET_RESULT New, RESET_RESULT old) { Exit( ((New==old) ? S+"Can't set display mode: "+AsText(New) : S+"Can't set new display mode: "+AsText(New) +"\nCan't set old display mode: "+AsText(old)) #if DX9 +((New==RESET_DEVICE_RESET_FAILED || old==RESET_DEVICE_RESET_FAILED) ? "\nDid you forget to delete IMAGE_RT Image in D.lost on DX9?" : "") #endif ); } #if DX11 static Bool ResizeTarget() { #if WINDOWS_OLD again: if(OK(SwapChain->ResizeTarget(&SwapChainDesc.BufferDesc)))return true; if(SwapChainDesc.BufferDesc.Format==DXGI_FORMAT_R10G10B10A2_UNORM){SwapChainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; goto again;} // if failed with 10-bit then try again with 8-bit #endif return false; } static Bool ResizeBuffers() { again: #if WINDOWS_OLD if(OK(SwapChain->ResizeBuffers(SwapChainDesc.BufferCount, SwapChainDesc.BufferDesc.Width, SwapChainDesc.BufferDesc.Height, SwapChainDesc.BufferDesc.Format, SwapChainDesc.Flags)))return true; if(SwapChainDesc.BufferDesc.Format==DXGI_FORMAT_R10G10B10A2_UNORM){SwapChainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; goto again;} // if failed with 10-bit then try again with 8-bit #else if(OK(SwapChain->ResizeBuffers(SwapChainDesc.BufferCount, SwapChainDesc. Width, SwapChainDesc. Height, SwapChainDesc. Format, SwapChainDesc.Flags)))return true; if(SwapChainDesc.Format==DXGI_FORMAT_R10G10B10A2_UNORM){SwapChainDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; goto again;} // if failed with 10-bit then try again with 8-bit #endif return false; } #endif Display::RESET_RESULT Display::ResetTry() { SyncLocker locker(_lock); _resetting=true; RESET_RESULT result; if(!created())result=RESET_DEVICE_NOT_CREATED;else { Bool begin_end=_began; if( begin_end)end(); Renderer.rtDel(); #if DX9 VI.lost(); // dynamic buffers VideoTexturesLost(); // video textures (render targets) RELEASE(Query); #endif if(lost && !lost())result=RESET_CUSTOM_LOST_FAILED;else { Bool ok=true; #if WINDOWS #if (DX9 || DX11) && !WINDOWS_NEW // on WindowsNew we can't change mode here, we need to set according to what we've got, instead 'RequestDisplayMode' can be called if(!exclusive())ok=SetDisplayMode(); #endif #if DX9 if(ok) { reset:; ok=OK(D3D->Reset(&D3DPP)); if(!ok && WindowActive()!=App.hwnd()) // if reset failed and we're not focused then wait a little and try again (this is important when using 'BackgroundLoader' and pressing Ctrl+Alt+Del, device is lost, fails to reset and secondary thread fails to load data) { Time.wait(100); goto reset; } setDeviceSettings(); } #elif DX11 #if WINDOWS_OLD if(ok&=OK(SwapChain->SetFullscreenState(!SwapChainDesc.Windowed, SwapChainDesc.Windowed ? null : Output))) if(ok&=ResizeTarget()) #elif 0 // both 'SetFullscreenState' and 'ResizeTarget' fail on WINDOWS_NEW, instead, 'TryEnterFullScreenMode', 'ExitFullScreenMode', 'TryResizeView' are used DXGI_MODE_DESC mode; Zero(mode); mode.Format =SwapChainDesc.Format; mode.Width =SwapChainDesc.Width; mode.Height =SwapChainDesc.Height; mode.Scaling=DXGI_MODE_SCALING_UNSPECIFIED; if(_freq_want && full()) // set custom frequency only if desired and in full-screen { mode.RefreshRate.Numerator =_freq_want; mode.RefreshRate.Denominator=1; } if(ok&=OK(SwapChain->SetFullscreenState(full(), null))) if(ok&=OK(SwapChain->ResizeTarget(&mode))) #endif { // 'ResizeTarget' may select a different resolution than requested for fullscreen mode, so check what we've got #if WINDOWS_OLD if(!SwapChainDesc.Windowed) #else if(0) // if(full()) this shouldn't be performed for WINDOWS_NEW because for this we're not setting a custom display mode, but instead we're setting to what we've got #endif { IDXGIOutput *output=null; SwapChain->GetContainingOutput(&output); if(output) { DXGI_OUTPUT_DESC desc; if(OK(output->GetDesc(&desc))) { _res.set(desc.DesktopCoordinates.right-desc.DesktopCoordinates.left, desc.DesktopCoordinates.bottom-desc.DesktopCoordinates.top); #if WINDOWS_OLD SwapChainDesc.BufferDesc.Width =resW(); SwapChainDesc.BufferDesc.Height=resH(); #else // for WINDOWS_NEW this should adjust '_res' based on relative rotation SwapChainDesc.Width =resW(); SwapChainDesc.Height=resH(); #endif densityUpdate(); } output->Release(); } } ok&=ResizeBuffers(); } #elif GL if(ok)ok=SetDisplayMode(); #endif #elif MAC || LINUX ok=SetDisplayMode(); #else ok=true; #endif getCaps(); if(!ok )result=RESET_DEVICE_RESET_FAILED ;else if(!Renderer.rtCreate())result=RESET_RENDER_TARGET_FAILED;else { #if DX9 D3D->CreateQuery(D3DQUERYTYPE_EVENT, &Query); VI.reset(); // dynamic buffers #endif adjustWindow(); // !! call before 'after' so current monitor can be detected properly based on window position which affects the aspect ratio in 'after' !! after(true); begin(); // we need begin if 'begin_end' was enabled and also always for the following code { resetEyeAdaptation(); // make sure we're inside begin because this potentially may use drawing #if DX9 VideoTexturesReset(); // video textures (render targets), make sure we're inside begin #endif } if(!begin_end)end(); // leave in the same state as before the reset Time.skipUpdate(2); // when resetting display skip 2 frames, because the slow down can occur for this long if(reset && !reset())result=RESET_CUSTOM_RESET_FAILED; else result=RESET_OK; } } } _resetting=false; return result; } void Display::Reset() { RESET_RESULT result=ResetTry(); if(result!=RESET_OK)ResetFailed(result, result); } /******************************************************************************/ void Display::getGamma() { Bool ok=false; #if WINDOWS_OLD if(HDC hdc=GetDC(null)){ok=(GetDeviceGammaRamp(hdc, _gamma_array)!=0); ReleaseDC(null, hdc);} #elif MAC Int capacity =CGDisplayGammaTableCapacity(kCGDirectMainDisplay); if( capacity>=1) { Memc r, g, b; r.setNum(capacity); g.setNum(capacity); b.setNum(capacity); UInt samples=0; CGGetDisplayTransferByTable(kCGDirectMainDisplay, capacity, r.data(), g.data(), b.data(), &samples); if( samples>1 && samples<=capacity) { ok=true; REP(256) { Int src=i*(samples-1)/255; _gamma_array[0][i]=RoundU(Sat(r[src])*0xFFFF); _gamma_array[1][i]=RoundU(Sat(g[src])*0xFFFF); _gamma_array[2][i]=RoundU(Sat(b[src])*0xFFFF); } } } #elif LINUX if(XDisplay) { Int size=0; if(XF86VidModeGetGammaRampSize(XDisplay, DefaultScreen(XDisplay), &size)) { if(size==256) { ok=(XF86VidModeGetGammaRamp(XDisplay, DefaultScreen(XDisplay), 256, _gamma_array[0], _gamma_array[1], _gamma_array[2])!=0); }else if(size>0) { Memc r, g, b; r.setNum(size); g.setNum(size); b.setNum(size); if(XF86VidModeGetGammaRamp(XDisplay, DefaultScreen(XDisplay), size, r.data(), g.data(), b.data())) { ok=true; REP(256) { Int src=i*(size-1)/255; _gamma_array[0][i]=r[src]; _gamma_array[1][i]=g[src]; _gamma_array[2][i]=b[src]; } } } } } #endif if(!ok)REP(256)_gamma_array[0][i]=_gamma_array[1][i]=_gamma_array[2][i]=(i*0xFFFF+128)/255; } void Display::getCaps() { if(LogInit)LogN("Display.getCaps"); #if DX9 D3DDISPLAYMODE DM ; D3D->GetDisplayMode(0, &DM); D3DCAPS9 caps; D3D->GetDeviceCaps ( &caps); _freq_got =DM.RefreshRate; _max_rt =Mid(caps.NumSimultaneousRTs, 1, 255); _max_tex_filter =Mid(caps.MaxAnisotropy , 1, 255); _max_tex_size =Min(Int(caps.MaxTextureWidth), Int(caps.MaxTextureHeight)); _tex_pow2 =(FlagTest(caps.TextureCaps, D3DPTEXTURECAPS_NONPOW2CONDITIONAL) ? 1 : FlagTest(caps.TextureCaps, D3DPTEXTURECAPS_POW2) ? 2 : 0); // 0=non-pow2 supported, 1=non-pow2 conditional, 2=non-pow2 not supported (pow2 required) _tex_pow2_3d = FlagTest(caps.TextureCaps, D3DPTEXTURECAPS_VOLUMEMAP_POW2); _tex_pow2_cube = FlagTest(caps.TextureCaps, D3DPTEXTURECAPS_CUBEMAP_POW2 ); _shader_tex_lod =(D3DSHADER_VERSION_MAJOR(caps.PixelShaderVersion)>=3); _mrt_const_bit_size=((caps.PrimitiveMiscCaps&D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS )==0); _mrt_post_process =((caps.PrimitiveMiscCaps&D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING)!=0 && validUsage(D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, IMAGE_B8G8R8A8) && validUsage(D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, IMAGE_F32 ) && validUsage(D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, IMAGE_F16_4 )); #elif DX11 // values taken from - https://msdn.microsoft.com/en-us/library/windows/desktop/ff476876(v=vs.85).aspx DXGI_SWAP_CHAIN_DESC desc; SwapChain->GetDesc(&desc); _freq_got=(desc.BufferDesc.RefreshRate.Denominator ? RoundPos(Flt(desc.BufferDesc.RefreshRate.Numerator)/desc.BufferDesc.RefreshRate.Denominator) : 0); _max_rt =((FeatureLevel>=D3D_FEATURE_LEVEL_10_0) ? 8 : (FeatureLevel>=D3D_FEATURE_LEVEL_9_3) ? 4 : 1); _max_tex_filter =((FeatureLevel>=D3D_FEATURE_LEVEL_9_2 ) ? 16 : 2); _max_tex_size =((FeatureLevel>=D3D_FEATURE_LEVEL_11_0) ? 16384 : (FeatureLevel>=D3D_FEATURE_LEVEL_10_0) ? 8192 : (FeatureLevel>=D3D_FEATURE_LEVEL_9_3) ? 4096 : 2048); _tex_pow2 = // 0=non-pow2 supported, 1=non-pow2 conditional, 2=non-pow2 not supported (pow2 required) _tex_pow2_3d = _tex_pow2_cube =(FeatureLevel<=D3D_FEATURE_LEVEL_9_3); _shader_tex_lod =(FeatureLevel>=D3D_FEATURE_LEVEL_10_0); _mrt_const_bit_size=false; _mrt_post_process =true ; #elif GL CChar8 *ext=(CChar8*)glGetString(GL_EXTENSIONS); _max_tex_size =2048; glGetIntegerv(GL_MAX_TEXTURE_SIZE , &_max_tex_size ); int aniso = 16; glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY, & aniso ); _max_tex_filter =Mid(aniso , 1, 255); int max_vtx_attrib = 0; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS , & max_vtx_attrib ); _max_vtx_attribs=Mid(max_vtx_attrib, 0, 255); if(shaderModelGLES2())_max_rt=1;else // we don't support MRT on GLES2 (there's no 'glDrawBuffers' function) { int max_draw_buffers=1; glGetIntegerv(GL_MAX_DRAW_BUFFERS , &max_draw_buffers); int max_col_attach =1; glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_col_attach ); _max_rt=Mid(Min(max_draw_buffers, max_col_attach), 1, 255); } Bool npot=(notShaderModelGLES2() || Contains(ext, "GL_OES_texture_npot", false, true)); _tex_pow2 =((npot || Contains(ext, "GL_NV_texture_npot_2D_mipmap", false, true)) ? 0 : 1); // 0=non-pow2 supported, 1=non-pow2 conditional, 2=non-pow2 not supported (pow2 required) _tex_pow2_3d = !npot; _tex_pow2_cube= !npot; _shader_tex_lod =(notShaderModelGLES2() || Contains(ext, "GL_ARB_shader_texture_lod", false, true) || Contains(ext, "GL_EXT_shader_texture_lod", false, true)); _mrt_const_bit_size=false; _mrt_post_process =true ; #if VARIABLE_MAX_MATRIX int max_vs_vectors=0, max_ps_vectors=0; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS , &max_vs_vectors); glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &max_ps_vectors); MeshBoneSplit=(Min(max_vs_vectors, max_ps_vectors)<768+256+256); // 768 for ObjMatrix, 256 for ObjVel, 256 extra #endif #endif #if IOS _freq_got=[UIScreen mainScreen].maximumFramesPerSecond; #elif LINUX if(XDisplay) { int dotclock; XF86VidModeModeLine mode; if(XF86VidModeGetModeLine(XDisplay, DefaultScreen(XDisplay), &dotclock, &mode)) { int total=mode.htotal*mode.vtotal; _freq_got=(total ? dotclock*1000/total : 0); } } #endif if(!Physics.precision())Physics.precision(0); // adjust physics precision when possibility of screen refresh rate change densityUpdate(); // max texture size affects max allowed density if(_mrt_const_bit_size)_hp_col_rt=_hp_nrm_rt=false; // no need to disable '_hp_lum_rt' and '_lit_col_rt_prec' because those RT's are not used as MRT _samples=DisplaySamples(_samples); if(Renderer.anyDeferred() && deferredUnavailable()) { if(Renderer.type ()==RT_DEFERRED)Renderer.type (RT_FORWARD); if(Water .reflectionRenderer()==RT_DEFERRED)Water .reflectionRenderer(RT_FORWARD); } } /******************************************************************************/ void Display::after(Bool resize_callback) { if(LogInit)LogN("Display.after"); if(!full() // if we're setting window #if !WEB // for WEB set size even if we're maximized, because for WEB 'App.maximized' means the browser window and not the canvas size && !App.maximized() // which is not maximized #endif )App._window_size=res(); if(_gamma)gammaSet(); // force reset gamma aspectRatioEx(true, !resize_callback); } /******************************************************************************/ void Display::begin() { #if DX9 if(D3D) { D3D->BeginScene(); D._began=true; } #endif } void Display::end() { #if DX9 if(D3D) { D3D->EndScene(); D._began=false; } #endif } Bool Display::flip() { if(created()) { #if DX9 end(); Bool ok=OK(D3D->Present(null, null, null, null)); begin(); return ok; #elif DX11 Bool sync=ActualSync(); if(!OK(SwapChain->Present(sync, sync ? 0 : PresentFlags)))return false; // we can use 'DXGI_PRESENT_ALLOW_TEARING' only when "sync==false", do this extra check here, because 'ActualSync' depends on VR which may disconnect after 'SwapChain' was created and 'PresentFlags' already set if(SwapChainDesc.SwapEffect!=DXGI_SWAP_EFFECT_DISCARD) // when using swap chain flip mode, 'Present' clears the backbuffer from 'OMSetRenderTargets', so reset it D3DC->OMSetRenderTargets(Elms(Renderer._cur_id), Renderer._cur_id, Renderer._cur_ds_id); #elif GL #if WINDOWS SwapBuffers(hDC); #elif MAC CGLFlushDrawable(MainContext.context); // same as "[[OpenGLView openGLContext] flushBuffer];" #elif LINUX glXSwapBuffers(XDisplay, App.Hwnd()); #elif ANDROID eglSwapBuffers(GLDisplay, MainContext.surface); #elif IOS glBindRenderbuffer(GL_RENDERBUFFER, Renderer._main._rb); [MainContext.context presentRenderbuffer:GL_RENDERBUFFER]; #elif WEB // this is done automatically on Web #endif #endif } return true; } void Display::flush() { if(created()) { #if DX9 if(Query) { Query->Issue(D3DISSUE_END); while(S_FALSE==Query->GetData(null, 0, D3DGETDATA_FLUSH)); } #elif DX11 D3DC->Flush(); #elif GL glFlush(); #endif } } void Display::finish() { if(created()) { #if DX9 if(Query) { Query->Issue(D3DISSUE_END); while(S_FALSE==Query->GetData(null, 0, D3DGETDATA_FLUSH)); } #elif DX11 if(Query) { D3DC->End(Query); BOOL done=FALSE; while(OK(D3DC->GetData(Query, &done, SIZE(done), 0)) && !done); } #elif GL glFinish(); #endif } } /******************************************************************************/ // SETTINGS /******************************************************************************/ void Display::adjustWindow() { RectI full, work; VecI2 max_normal_win_client_size, maximized_win_client_size; #if FORCE_MAIN_DISPLAY if(D.full())mainMonitor(full, work, max_normal_win_client_size, maximized_win_client_size);else #endif curMonitor(full, work, max_normal_win_client_size, maximized_win_client_size); #if DEBUG && 0 LogN(S+"full:"+full.asText()+", work:"+work.asText()+", App._window_pos:"+App._window_pos+", D.res:"+D.res()); #endif #if WINDOWS_OLD if(D.full()) // fullscreen { SetWindowLongPtr(App.Hwnd(), GWL_STYLE , App._style_full); SetWindowPos (App.Hwnd(), HWND_TOPMOST, full.min.x, full.min.y, resW(), resH(), 0); }else if(resW()>=maximized_win_client_size.x && resH()>=maximized_win_client_size.y) // maximized { SetWindowLongPtr(App.Hwnd(), GWL_STYLE, App._style_window_maximized); #if 0 // this doesn't work as expected SetWindowPos (App.Hwnd(), HWND_TOP , work.min.x+App._bound_maximized.min.x, work.min.y-App._bound_maximized.max.y, resW()+App._bound_maximized.w(), resH()+App._bound_maximized.h(), SWP_NOACTIVATE); #else SetWindowPos (App.Hwnd(), HWND_TOP , work.min.x+App._bound_maximized.min.x, work.min.y-App._bound_maximized.max.y, resW()+App._bound_maximized.max.x, resH()-App._bound_maximized.min.y, SWP_NOACTIVATE); #endif }else // normal window { Int w=resW()+App._bound.w(), h=resH()+App._bound.h(); if(App._window_pos.x==INT_MAX){if(App.x<=-1)App._window_pos.x=work.min.x;else if(!App.x)App._window_pos.x=work.centerXI()-w/2;else App._window_pos.x=work.max.x-w;} if(App._window_pos.y==INT_MAX){if(App.y>= 1)App._window_pos.y=work.min.y;else if(!App.y)App._window_pos.y=work.centerYI()-h/2;else App._window_pos.y=work.max.y-h;} // make sure the window is not completely outside of working area const Int b=32; Int r=b; if(!(App.flag&APP_NO_TITLE_BAR)) // has bar { Int size=GetSystemMetrics(SM_CXSIZE); // TODO: this should be OK because we're DPI-Aware, however it doesn't work OK /*if(HDC hdc=GetDC(App.Hwnd())) { size=DivCeil(size*GetDeviceCaps(hdc, LOGPIXELSX), 96); ReleaseDC(App.Hwnd(), hdc); }*/ if(!(App.flag& APP_NO_CLOSE ))r+=size ; // has close button if( App.flag&(APP_MINIMIZABLE|APP_MAXIMIZABLE))r+=size*2; // has minimize or maximize button (if any is enabled, then both will appear) } if(App._window_pos.x+b>work.max.x)App._window_pos.x=Max(work.min.x, work.max.x-b);else{Int p=App._window_pos.x+w; if(p-rwork.max.y)App._window_pos.y=Max(work.min.y, work.max.y-b);else{Int p=App._window_pos.y+h; if(p-b