mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-01-31 04:50:48 +00:00
- force render buffers to be active.
For modern hardware the fallback path really makes no sense and this allows to simplify some things quite a bit.
This commit is contained in:
parent
8ab68264c1
commit
9ff7e5a4ef
12 changed files with 32 additions and 109 deletions
|
@ -394,7 +394,7 @@ void FGLRenderer::BlurScene(float gameinfobluramount)
|
||||||
blurAmount = gameinfobluramount;
|
blurAmount = gameinfobluramount;
|
||||||
|
|
||||||
// if blurAmount == 0 or somehow still returns negative, exit to prevent a crash, clearly we don't want this
|
// if blurAmount == 0 or somehow still returns negative, exit to prevent a crash, clearly we don't want this
|
||||||
if ((blurAmount <= 0.0) || !FGLRenderBuffers::IsEnabled())
|
if (blurAmount <= 0.0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FGLDebug::PushGroup("BlurScene");
|
FGLDebug::PushGroup("BlurScene");
|
||||||
|
@ -662,7 +662,7 @@ void FGLRenderer::Flush()
|
||||||
const auto &mSceneViewport = screen->mSceneViewport;
|
const auto &mSceneViewport = screen->mSceneViewport;
|
||||||
const auto &mScreenViewport = screen->mScreenViewport;
|
const auto &mScreenViewport = screen->mScreenViewport;
|
||||||
|
|
||||||
if (stereo3dMode.IsMono() || !FGLRenderBuffers::IsEnabled())
|
if (stereo3dMode.IsMono())
|
||||||
{
|
{
|
||||||
CopyToBackbuffer(nullptr, true);
|
CopyToBackbuffer(nullptr, true);
|
||||||
}
|
}
|
||||||
|
@ -701,30 +701,22 @@ void FGLRenderer::CopyToBackbuffer(const IntRect *bounds, bool applyGamma)
|
||||||
mCustomPostProcessShaders->Run("screen");
|
mCustomPostProcessShaders->Run("screen");
|
||||||
|
|
||||||
FGLDebug::PushGroup("CopyToBackbuffer");
|
FGLDebug::PushGroup("CopyToBackbuffer");
|
||||||
if (FGLRenderBuffers::IsEnabled())
|
FGLPostProcessState savedState;
|
||||||
|
mBuffers->BindOutputFB();
|
||||||
|
|
||||||
|
IntRect box;
|
||||||
|
if (bounds)
|
||||||
{
|
{
|
||||||
FGLPostProcessState savedState;
|
box = *bounds;
|
||||||
mBuffers->BindOutputFB();
|
|
||||||
|
|
||||||
IntRect box;
|
|
||||||
if (bounds)
|
|
||||||
{
|
|
||||||
box = *bounds;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ClearBorders();
|
|
||||||
box = screen->mOutputLetterbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
mBuffers->BindCurrentTexture(0);
|
|
||||||
DrawPresentTexture(box, applyGamma);
|
|
||||||
}
|
}
|
||||||
else if (!bounds)
|
else
|
||||||
{
|
{
|
||||||
FGLPostProcessState savedState;
|
|
||||||
ClearBorders();
|
ClearBorders();
|
||||||
|
box = screen->mOutputLetterbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mBuffers->BindCurrentTexture(0);
|
||||||
|
DrawPresentTexture(box, applyGamma);
|
||||||
FGLDebug::PopGroup();
|
FGLDebug::PopGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
||||||
CVAR(Bool, gl_renderbuffers, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL)
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
|
@ -173,17 +172,6 @@ void FGLRenderBuffers::DeleteFrameBuffer(PPFrameBuffer &fb)
|
||||||
|
|
||||||
bool FGLRenderBuffers::Setup(int width, int height, int sceneWidth, int sceneHeight)
|
bool FGLRenderBuffers::Setup(int width, int height, int sceneWidth, int sceneHeight)
|
||||||
{
|
{
|
||||||
if (gl_renderbuffers != BuffersActive)
|
|
||||||
{
|
|
||||||
if (BuffersActive)
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
BuffersActive = gl_renderbuffers;
|
|
||||||
GLRenderer->mShaderManager->ResetFixedColormap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsEnabled())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (width <= 0 || height <= 0)
|
if (width <= 0 || height <= 0)
|
||||||
I_FatalError("Requested invalid render buffer sizes: screen = %dx%d", width, height);
|
I_FatalError("Requested invalid render buffer sizes: screen = %dx%d", width, height);
|
||||||
|
|
||||||
|
@ -234,9 +222,10 @@ bool FGLRenderBuffers::Setup(int width, int height, int sceneWidth, int sceneHei
|
||||||
mSamples = 0;
|
mSamples = 0;
|
||||||
mSceneWidth = 0;
|
mSceneWidth = 0;
|
||||||
mSceneHeight = 0;
|
mSceneHeight = 0;
|
||||||
|
I_FatalError("Unable to create render buffers.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return !FailedCreate;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -615,7 +604,7 @@ bool FGLRenderBuffers::CheckFrameBufferCompleteness()
|
||||||
if (result == GL_FRAMEBUFFER_COMPLETE)
|
if (result == GL_FRAMEBUFFER_COMPLETE)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
FailedCreate = true;
|
bool FailedCreate = true;
|
||||||
|
|
||||||
if (gl_debug_level > 0)
|
if (gl_debug_level > 0)
|
||||||
{
|
{
|
||||||
|
@ -926,10 +915,4 @@ void FGLRenderBuffers::BindOutputFB()
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
bool FGLRenderBuffers::IsEnabled()
|
|
||||||
{
|
|
||||||
return BuffersActive && !FailedCreate;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FGLRenderBuffers::FailedCreate = false;
|
bool FGLRenderBuffers::FailedCreate = false;
|
||||||
bool FGLRenderBuffers::BuffersActive = false;
|
|
||||||
|
|
|
@ -192,7 +192,6 @@ private:
|
||||||
int mCurrentShadowMapSize = 0;
|
int mCurrentShadowMapSize = 0;
|
||||||
|
|
||||||
static bool FailedCreate;
|
static bool FailedCreate;
|
||||||
static bool BuffersActive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -236,7 +236,7 @@ bool FRenderState::ApplyShader()
|
||||||
}
|
}
|
||||||
else if ((mColormapState >= CM_FIRSTSPECIALCOLORMAP && mColormapState < CM_MAXCOLORMAPFORCED))
|
else if ((mColormapState >= CM_FIRSTSPECIALCOLORMAP && mColormapState < CM_MAXCOLORMAPFORCED))
|
||||||
{
|
{
|
||||||
if (FGLRenderBuffers::IsEnabled() && mColormapState < CM_FIRSTSPECIALCOLORMAPFORCED)
|
if (mColormapState < CM_FIRSTSPECIALCOLORMAPFORCED)
|
||||||
{
|
{
|
||||||
// When using postprocessing to apply the colormap, we must render the image fullbright here.
|
// When using postprocessing to apply the colormap, we must render the image fullbright here.
|
||||||
activeShader->muFixedColormap.Set(2);
|
activeShader->muFixedColormap.Set(2);
|
||||||
|
|
|
@ -488,7 +488,7 @@ void GLSceneDrawer::EndDrawScene(FDrawInfo *di, sector_t * viewsector)
|
||||||
Reset3DViewport();
|
Reset3DViewport();
|
||||||
|
|
||||||
// Delay drawing psprites until after bloom has been applied, if enabled.
|
// Delay drawing psprites until after bloom has been applied, if enabled.
|
||||||
if (!FGLRenderBuffers::IsEnabled() || !gl_bloom || FixedColormap != CM_DEFAULT)
|
if (!gl_bloom || FixedColormap != CM_DEFAULT)
|
||||||
{
|
{
|
||||||
DrawEndScene2D(di, viewsector);
|
DrawEndScene2D(di, viewsector);
|
||||||
}
|
}
|
||||||
|
@ -659,7 +659,7 @@ sector_t * GLSceneDrawer::RenderViewpoint (AActor * camera, IntRect * bounds, fl
|
||||||
ProcessScene(di, toscreen);
|
ProcessScene(di, toscreen);
|
||||||
if (mainview && toscreen) EndDrawScene(di, lviewsector); // do not call this for camera textures.
|
if (mainview && toscreen) EndDrawScene(di, lviewsector); // do not call this for camera textures.
|
||||||
|
|
||||||
if (mainview && FGLRenderBuffers::IsEnabled())
|
if (mainview)
|
||||||
{
|
{
|
||||||
GLRenderer->PostProcessScene(FixedColormap, [&]() { if (gl_bloom && FixedColormap == CM_DEFAULT) DrawEndScene2D(di, lviewsector); });
|
GLRenderer->PostProcessScene(FixedColormap, [&]() { if (gl_bloom && FixedColormap == CM_DEFAULT) DrawEndScene2D(di, lviewsector); });
|
||||||
|
|
||||||
|
@ -676,7 +676,7 @@ sector_t * GLSceneDrawer::RenderViewpoint (AActor * camera, IntRect * bounds, fl
|
||||||
}
|
}
|
||||||
FDrawInfo::EndDrawInfo();
|
FDrawInfo::EndDrawInfo();
|
||||||
GLRenderer->mDrawingScene2D = false;
|
GLRenderer->mDrawingScene2D = false;
|
||||||
if (!stereo3dMode.IsMono() && FGLRenderBuffers::IsEnabled())
|
if (!stereo3dMode.IsMono())
|
||||||
GLRenderer->mBuffers->BlitToEyeTexture(eye_ix);
|
GLRenderer->mBuffers->BlitToEyeTexture(eye_ix);
|
||||||
eye->TearDown();
|
eye->TearDown();
|
||||||
}
|
}
|
||||||
|
@ -716,12 +716,6 @@ void GLSceneDrawer::WriteSavePic (player_t *player, FileWriter *file, int width,
|
||||||
glDisable(GL_STENCIL_TEST);
|
glDisable(GL_STENCIL_TEST);
|
||||||
gl_RenderState.SetFixedColormap(CM_DEFAULT);
|
gl_RenderState.SetFixedColormap(CM_DEFAULT);
|
||||||
gl_RenderState.SetSoftLightLevel(-1);
|
gl_RenderState.SetSoftLightLevel(-1);
|
||||||
if (!FGLRenderBuffers::IsEnabled())
|
|
||||||
{
|
|
||||||
// Since this doesn't do any of the 2D rendering it needs to draw the screen blend itself before extracting the image.
|
|
||||||
screen->DrawBlend(viewsector);
|
|
||||||
screen->Draw2D();
|
|
||||||
}
|
|
||||||
GLRenderer->CopyToBackbuffer(&bounds, false);
|
GLRenderer->CopyToBackbuffer(&bounds, false);
|
||||||
|
|
||||||
// strictly speaking not needed as the glReadPixels should block until the scene is rendered, but this is to safeguard against shitty drivers
|
// strictly speaking not needed as the glReadPixels should block until the scene is rendered, but this is to safeguard against shitty drivers
|
||||||
|
|
|
@ -179,12 +179,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char *
|
||||||
unsigned int lightbuffersize = GLRenderer->mLights->GetBlockSize();
|
unsigned int lightbuffersize = GLRenderer->mLights->GetBlockSize();
|
||||||
if (lightbuffertype == GL_UNIFORM_BUFFER)
|
if (lightbuffertype == GL_UNIFORM_BUFFER)
|
||||||
{
|
{
|
||||||
{
|
vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n", lightbuffersize);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n", lightbuffersize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -238,21 +238,13 @@ uint32_t OpenGLFrameBuffer::GetCaps()
|
||||||
|
|
||||||
// describe our basic feature set
|
// describe our basic feature set
|
||||||
ActorRenderFeatureFlags FlagSet = RFF_FLATSPRITES | RFF_MODELS | RFF_SLOPE3DFLOORS |
|
ActorRenderFeatureFlags FlagSet = RFF_FLATSPRITES | RFF_MODELS | RFF_SLOPE3DFLOORS |
|
||||||
RFF_TILTPITCH | RFF_ROLLSPRITES | RFF_POLYGONAL;
|
RFF_TILTPITCH | RFF_ROLLSPRITES | RFF_POLYGONAL | RFF_MATSHADER | RFF_POSTSHADER | RFF_BRIGHTMAP;
|
||||||
if (r_drawvoxels)
|
if (r_drawvoxels)
|
||||||
FlagSet |= RFF_VOXELS;
|
FlagSet |= RFF_VOXELS;
|
||||||
|
|
||||||
if (!RenderBuffersEnabled())
|
if (gl_tonemap != 5) // not running palette tonemap shader
|
||||||
{
|
FlagSet |= RFF_TRUECOLOR;
|
||||||
// truecolor is always available when renderbuffers are unavailable because palette tonemap is not possible
|
|
||||||
FlagSet |= RFF_TRUECOLOR | RFF_MATSHADER | RFF_BRIGHTMAP;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (gl_tonemap != 5) // not running palette tonemap shader
|
|
||||||
FlagSet |= RFF_TRUECOLOR;
|
|
||||||
FlagSet |= RFF_MATSHADER | RFF_POSTSHADER | RFF_BRIGHTMAP;
|
|
||||||
}
|
|
||||||
return (uint32_t)FlagSet;
|
return (uint32_t)FlagSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,11 +385,6 @@ void OpenGLFrameBuffer::BlurScene(float amount)
|
||||||
GLRenderer->BlurScene(amount);
|
GLRenderer->BlurScene(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLFrameBuffer::RenderBuffersEnabled()
|
|
||||||
{
|
|
||||||
return FGLRenderBuffers::IsEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLFrameBuffer::SetViewportRects(IntRect *bounds)
|
void OpenGLFrameBuffer::SetViewportRects(IntRect *bounds)
|
||||||
{
|
{
|
||||||
Super::SetViewportRects(bounds);
|
Super::SetViewportRects(bounds);
|
||||||
|
|
|
@ -41,7 +41,6 @@ public:
|
||||||
void TextureFilterChanged() override;
|
void TextureFilterChanged() override;
|
||||||
void ResetFixedColormap() override;
|
void ResetFixedColormap() override;
|
||||||
void BeginFrame() override;
|
void BeginFrame() override;
|
||||||
bool RenderBuffersEnabled() override;
|
|
||||||
void SetViewportRects(IntRect *bounds) override;
|
void SetViewportRects(IntRect *bounds) override;
|
||||||
void BlurScene(float amount) override;
|
void BlurScene(float amount) override;
|
||||||
IUniformBuffer *CreateUniformBuffer(size_t size, bool staticuse = false) override;
|
IUniformBuffer *CreateUniformBuffer(size_t size, bool staticuse = false) override;
|
||||||
|
|
|
@ -128,19 +128,8 @@ bool OpenGLFrameBuffer::WipeStartScreen(int type)
|
||||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (FGLRenderBuffers::IsEnabled())
|
GLRenderer->mBuffers->BindCurrentFB();
|
||||||
{
|
copyPixels();
|
||||||
GLRenderer->mBuffers->BindCurrentFB();
|
|
||||||
copyPixels();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GLint readbuffer = 0;
|
|
||||||
glGetIntegerv(GL_READ_BUFFER, &readbuffer);
|
|
||||||
glReadBuffer(GL_FRONT);
|
|
||||||
copyPixels();
|
|
||||||
glReadBuffer(readbuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
@ -166,8 +155,7 @@ void OpenGLFrameBuffer::WipeEndScreen()
|
||||||
glFinish();
|
glFinish();
|
||||||
wipeendscreen->Bind(0, false, false);
|
wipeendscreen->Bind(0, false, false);
|
||||||
|
|
||||||
if (FGLRenderBuffers::IsEnabled())
|
GLRenderer->mBuffers->BindCurrentFB();
|
||||||
GLRenderer->mBuffers->BindCurrentFB();
|
|
||||||
|
|
||||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
@ -199,12 +187,9 @@ bool OpenGLFrameBuffer::WipeDo(int ticks)
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
glDepthMask(false);
|
glDepthMask(false);
|
||||||
|
|
||||||
if (FGLRenderBuffers::IsEnabled())
|
GLRenderer->mBuffers->BindCurrentFB();
|
||||||
{
|
const auto &bounds = screen->mScreenViewport;
|
||||||
GLRenderer->mBuffers->BindCurrentFB();
|
glViewport(bounds.left, bounds.top, bounds.width, bounds.height);
|
||||||
const auto &bounds = screen->mScreenViewport;
|
|
||||||
glViewport(bounds.left, bounds.top, bounds.width, bounds.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
done = ScreenWipe->Run(ticks, this);
|
done = ScreenWipe->Run(ticks, this);
|
||||||
glDepthMask(true);
|
glDepthMask(true);
|
||||||
|
|
|
@ -34,7 +34,6 @@ EXTERN_CVAR(Bool, gl_seamless)
|
||||||
EXTERN_CVAR(Float, gl_mask_threshold)
|
EXTERN_CVAR(Float, gl_mask_threshold)
|
||||||
EXTERN_CVAR(Float, gl_mask_sprite_threshold)
|
EXTERN_CVAR(Float, gl_mask_sprite_threshold)
|
||||||
|
|
||||||
EXTERN_CVAR(Bool, gl_renderbuffers)
|
|
||||||
EXTERN_CVAR(Int, gl_multisample)
|
EXTERN_CVAR(Int, gl_multisample)
|
||||||
|
|
||||||
EXTERN_CVAR(Bool, gl_bloom)
|
EXTERN_CVAR(Bool, gl_bloom)
|
||||||
|
|
|
@ -1072,7 +1072,7 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds)
|
||||||
bool notScaled = ((mScreenViewport.width == ViewportScaledWidth(mScreenViewport.width, mScreenViewport.height)) &&
|
bool notScaled = ((mScreenViewport.width == ViewportScaledWidth(mScreenViewport.width, mScreenViewport.height)) &&
|
||||||
(mScreenViewport.width == ViewportScaledHeight(mScreenViewport.width, mScreenViewport.height)) &&
|
(mScreenViewport.width == ViewportScaledHeight(mScreenViewport.width, mScreenViewport.height)) &&
|
||||||
!ViewportIsScaled43());
|
!ViewportIsScaled43());
|
||||||
if ((gl_scale_viewport && !IsFullscreen() && notScaled) || !RenderBuffersEnabled())
|
if (gl_scale_viewport && !IsFullscreen() && notScaled)
|
||||||
{
|
{
|
||||||
mScreenViewport.width = mOutputLetterbox.width;
|
mScreenViewport.width = mOutputLetterbox.width;
|
||||||
mScreenViewport.height = mOutputLetterbox.height;
|
mScreenViewport.height = mOutputLetterbox.height;
|
||||||
|
@ -1080,15 +1080,6 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds)
|
||||||
mSceneViewport.top = (int)round(mSceneViewport.top * scaleY);
|
mSceneViewport.top = (int)round(mSceneViewport.top * scaleY);
|
||||||
mSceneViewport.width = (int)round(mSceneViewport.width * scaleX);
|
mSceneViewport.width = (int)round(mSceneViewport.width * scaleX);
|
||||||
mSceneViewport.height = (int)round(mSceneViewport.height * scaleY);
|
mSceneViewport.height = (int)round(mSceneViewport.height * scaleY);
|
||||||
|
|
||||||
// Without render buffers we have to render directly to the letterbox
|
|
||||||
if (!RenderBuffersEnabled())
|
|
||||||
{
|
|
||||||
mScreenViewport.left += mOutputLetterbox.left;
|
|
||||||
mScreenViewport.top += mOutputLetterbox.top;
|
|
||||||
mSceneViewport.left += mOutputLetterbox.left;
|
|
||||||
mSceneViewport.top += mOutputLetterbox.top;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -412,7 +412,6 @@ public:
|
||||||
|
|
||||||
virtual int GetClientWidth() = 0;
|
virtual int GetClientWidth() = 0;
|
||||||
virtual int GetClientHeight() = 0;
|
virtual int GetClientHeight() = 0;
|
||||||
virtual bool RenderBuffersEnabled() { return false; };
|
|
||||||
virtual void BlurScene(float amount) {}
|
virtual void BlurScene(float amount) {}
|
||||||
|
|
||||||
// Interface to hardware rendering resources
|
// Interface to hardware rendering resources
|
||||||
|
|
Loading…
Reference in a new issue