All decisions were done deep inside the texture creation code, leaving zero options to the higher level code for controlling the feature. Changed this so that the option to upscale must be passed as a parameter to FRenderState::SetMaterial and extended all needed variables to manage the added texture variants. Still not solved: Material layers need explicit control, not only for scaling but also for filtering.
507 lines
14 KiB
C++
507 lines
14 KiB
C++
//
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Copyright(C) 2010-2016 Christoph Oelckers
|
|
// All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
/*
|
|
** gl_framebuffer.cpp
|
|
** Implementation of the non-hardware specific parts of the
|
|
** OpenGL frame buffer
|
|
**
|
|
*/
|
|
|
|
#include "gl_system.h"
|
|
#include "v_video.h"
|
|
#include "m_png.h"
|
|
#include "templates.h"
|
|
|
|
#include "gl_interface.h"
|
|
#include "gl/system/gl_framebuffer.h"
|
|
#include "gl/renderer/gl_renderer.h"
|
|
#include "gl/renderer/gl_renderbuffers.h"
|
|
#include "gl/textures/gl_samplers.h"
|
|
#include "hwrenderer/utility/hw_clock.h"
|
|
#include "hwrenderer/utility/hw_vrmodes.h"
|
|
#include "hwrenderer/models/hw_models.h"
|
|
#include "hwrenderer/scene/hw_skydome.h"
|
|
#include "hwrenderer/data/hw_viewpointbuffer.h"
|
|
#include "hwrenderer/dynlights/hw_lightbuffer.h"
|
|
#include "gl/shaders/gl_shaderprogram.h"
|
|
#include "gl_debug.h"
|
|
#include "r_videoscale.h"
|
|
#include "gl_buffers.h"
|
|
|
|
#include "hwrenderer/data/flatvertices.h"
|
|
|
|
EXTERN_CVAR (Bool, vid_vsync)
|
|
EXTERN_CVAR(Bool, r_drawvoxels)
|
|
EXTERN_CVAR(Int, gl_tonemap)
|
|
|
|
void gl_LoadExtensions();
|
|
void gl_PrintStartupLog();
|
|
void Draw2D(F2DDrawer *drawer, FRenderState &state);
|
|
|
|
extern bool vid_hdr_active;
|
|
|
|
namespace OpenGLRenderer
|
|
{
|
|
FGLRenderer *GLRenderer;
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
OpenGLFrameBuffer::OpenGLFrameBuffer(void *hMonitor, bool fullscreen) :
|
|
Super(hMonitor, fullscreen)
|
|
{
|
|
// SetVSync needs to be at the very top to workaround a bug in Nvidia's OpenGL driver.
|
|
// If wglSwapIntervalEXT is called after glBindFramebuffer in a frame the setting is not changed!
|
|
Super::SetVSync(vid_vsync);
|
|
|
|
// Make sure all global variables tracking OpenGL context state are reset..
|
|
FHardwareTexture::InitGlobalState();
|
|
gl_RenderState.Reset();
|
|
|
|
GLRenderer = nullptr;
|
|
}
|
|
|
|
OpenGLFrameBuffer::~OpenGLFrameBuffer()
|
|
{
|
|
PPResource::ResetAll();
|
|
|
|
if (mVertexData != nullptr) delete mVertexData;
|
|
if (mSkyData != nullptr) delete mSkyData;
|
|
if (mViewpoints != nullptr) delete mViewpoints;
|
|
if (mLights != nullptr) delete mLights;
|
|
mShadowMap.Reset();
|
|
|
|
if (GLRenderer)
|
|
{
|
|
delete GLRenderer;
|
|
GLRenderer = nullptr;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Initializes the GL renderer
|
|
//
|
|
//==========================================================================
|
|
|
|
void OpenGLFrameBuffer::InitializeState()
|
|
{
|
|
static bool first=true;
|
|
|
|
if (first)
|
|
{
|
|
if (ogl_LoadFunctions() == ogl_LOAD_FAILED)
|
|
{
|
|
I_FatalError("Failed to load OpenGL functions.");
|
|
}
|
|
}
|
|
|
|
gl_LoadExtensions();
|
|
|
|
// Move some state to the framebuffer object for easier access.
|
|
hwcaps = gl.flags;
|
|
glslversion = gl.glslversion;
|
|
uniformblockalignment = gl.uniformblockalignment;
|
|
maxuniformblock = gl.maxuniformblock;
|
|
vendorstring = gl.vendorstring;
|
|
|
|
if (first)
|
|
{
|
|
first=false;
|
|
gl_PrintStartupLog();
|
|
}
|
|
|
|
glDepthFunc(GL_LESS);
|
|
|
|
glEnable(GL_DITHER);
|
|
glDisable(GL_CULL_FACE);
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
glEnable(GL_POLYGON_OFFSET_LINE);
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_DEPTH_CLAMP);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_LINE_SMOOTH);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
glClearDepth(1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
SetViewportRects(nullptr);
|
|
|
|
mVertexData = new FFlatVertexBuffer(GetWidth(), GetHeight());
|
|
mSkyData = new FSkyVertexBuffer;
|
|
mViewpoints = new HWViewpointBuffer;
|
|
mLights = new FLightBuffer();
|
|
|
|
GLRenderer = new FGLRenderer(this);
|
|
GLRenderer->Initialize(GetWidth(), GetHeight());
|
|
|
|
static_cast<GLDataBuffer*>(mLights->GetBuffer())->BindBase();
|
|
|
|
mDebug = std::make_shared<FGLDebug>();
|
|
mDebug->Update();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Updates the screen
|
|
//
|
|
//==========================================================================
|
|
|
|
void OpenGLFrameBuffer::Update()
|
|
{
|
|
twoD.Reset();
|
|
Flush3D.Reset();
|
|
|
|
Flush3D.Clock();
|
|
GLRenderer->Flush();
|
|
Flush3D.Unclock();
|
|
|
|
Swap();
|
|
Super::Update();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Render the view to a savegame picture
|
|
//
|
|
//===========================================================================
|
|
|
|
void OpenGLFrameBuffer::WriteSavePic(player_t *player, FileWriter *file, int width, int height)
|
|
{
|
|
if (!V_IsHardwareRenderer())
|
|
Super::WriteSavePic(player, file, width, height);
|
|
else if (GLRenderer != nullptr)
|
|
GLRenderer->WriteSavePic(player, file, width, height);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
sector_t *OpenGLFrameBuffer::RenderView(player_t *player)
|
|
{
|
|
if (GLRenderer != nullptr)
|
|
return GLRenderer->RenderView(player);
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
uint32_t OpenGLFrameBuffer::GetCaps()
|
|
{
|
|
if (!V_IsHardwareRenderer())
|
|
return Super::GetCaps();
|
|
|
|
// describe our basic feature set
|
|
ActorRenderFeatureFlags FlagSet = RFF_FLATSPRITES | RFF_MODELS | RFF_SLOPE3DFLOORS |
|
|
RFF_TILTPITCH | RFF_ROLLSPRITES | RFF_POLYGONAL | RFF_MATSHADER | RFF_POSTSHADER | RFF_BRIGHTMAP;
|
|
if (r_drawvoxels)
|
|
FlagSet |= RFF_VOXELS;
|
|
|
|
if (gl_tonemap != 5) // not running palette tonemap shader
|
|
FlagSet |= RFF_TRUECOLOR;
|
|
|
|
return (uint32_t)FlagSet;
|
|
}
|
|
|
|
const char* OpenGLFrameBuffer::DeviceName() const
|
|
{
|
|
return gl.modelstring;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Swap the buffers
|
|
//
|
|
//==========================================================================
|
|
|
|
CVAR(Bool, gl_finishbeforeswap, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
|
|
|
void OpenGLFrameBuffer::Swap()
|
|
{
|
|
bool swapbefore = gl_finishbeforeswap && camtexcount == 0;
|
|
Finish.Reset();
|
|
Finish.Clock();
|
|
if (swapbefore) glFinish();
|
|
FPSLimit();
|
|
SwapBuffers();
|
|
if (!swapbefore) glFinish();
|
|
Finish.Unclock();
|
|
camtexcount = 0;
|
|
FHardwareTexture::UnbindAll();
|
|
mDebug->Update();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Enable/disable vertical sync
|
|
//
|
|
//==========================================================================
|
|
|
|
void OpenGLFrameBuffer::SetVSync(bool vsync)
|
|
{
|
|
// Switch to the default frame buffer because some drivers associate the vsync state with the bound FB object.
|
|
GLint oldDrawFramebufferBinding = 0, oldReadFramebufferBinding = 0;
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDrawFramebufferBinding);
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldReadFramebufferBinding);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
|
|
Super::SetVSync(vsync);
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDrawFramebufferBinding);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, oldReadFramebufferBinding);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void OpenGLFrameBuffer::CleanForRestart()
|
|
{
|
|
if (GLRenderer)
|
|
GLRenderer->ResetSWScene();
|
|
}
|
|
|
|
void OpenGLFrameBuffer::SetTextureFilterMode()
|
|
{
|
|
if (GLRenderer != nullptr && GLRenderer->mSamplerManager != nullptr) GLRenderer->mSamplerManager->SetTextureFilterMode();
|
|
}
|
|
|
|
IHardwareTexture *OpenGLFrameBuffer::CreateHardwareTexture()
|
|
{
|
|
return new FHardwareTexture(true/*tex->bNoCompress*/);
|
|
}
|
|
|
|
void OpenGLFrameBuffer::PrecacheMaterial(FMaterial *mat, int translation)
|
|
{
|
|
if (mat->Source()->GetUseType() == ETextureType::SWCanvas) return;
|
|
|
|
int flags = mat->GetScaleFlags();
|
|
int numLayers = mat->GetLayers();
|
|
FTexture* layer;
|
|
auto base = static_cast<FHardwareTexture*>(mat->GetLayer(0, translation, &layer));
|
|
|
|
if (base->BindOrCreate(layer, 0, CLAMP_NONE, translation, flags))
|
|
{
|
|
for (int i = 1; i < numLayers; i++)
|
|
{
|
|
auto systex = static_cast<FHardwareTexture*>(mat->GetLayer(i, 0, &layer));
|
|
systex->BindOrCreate(layer, i, CLAMP_NONE, 0, flags);
|
|
}
|
|
}
|
|
// unbind everything.
|
|
FHardwareTexture::UnbindAll();
|
|
}
|
|
|
|
FModelRenderer *OpenGLFrameBuffer::CreateModelRenderer(int mli)
|
|
{
|
|
return new FHWModelRenderer(nullptr, gl_RenderState, mli);
|
|
}
|
|
|
|
IVertexBuffer *OpenGLFrameBuffer::CreateVertexBuffer()
|
|
{
|
|
return new GLVertexBuffer;
|
|
}
|
|
|
|
IIndexBuffer *OpenGLFrameBuffer::CreateIndexBuffer()
|
|
{
|
|
return new GLIndexBuffer;
|
|
}
|
|
|
|
IDataBuffer *OpenGLFrameBuffer::CreateDataBuffer(int bindingpoint, bool ssbo, bool needsresize)
|
|
{
|
|
return new GLDataBuffer(bindingpoint, ssbo);
|
|
}
|
|
|
|
void OpenGLFrameBuffer::TextureFilterChanged()
|
|
{
|
|
if (GLRenderer != NULL && GLRenderer->mSamplerManager != NULL) GLRenderer->mSamplerManager->SetTextureFilterMode();
|
|
}
|
|
|
|
void OpenGLFrameBuffer::BlurScene(float amount)
|
|
{
|
|
GLRenderer->BlurScene(amount);
|
|
}
|
|
|
|
void OpenGLFrameBuffer::SetViewportRects(IntRect *bounds)
|
|
{
|
|
Super::SetViewportRects(bounds);
|
|
if (!bounds)
|
|
{
|
|
auto vrmode = VRMode::GetVRMode(true);
|
|
vrmode->AdjustViewport(this);
|
|
}
|
|
}
|
|
|
|
void OpenGLFrameBuffer::UpdatePalette()
|
|
{
|
|
if (GLRenderer)
|
|
GLRenderer->ClearTonemapPalette();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
void OpenGLFrameBuffer::BeginFrame()
|
|
{
|
|
SetViewportRects(nullptr);
|
|
if (GLRenderer != nullptr)
|
|
GLRenderer->BeginFrame();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Takes a screenshot
|
|
//
|
|
//===========================================================================
|
|
|
|
TArray<uint8_t> OpenGLFrameBuffer::GetScreenshotBuffer(int &pitch, ESSType &color_type, float &gamma)
|
|
{
|
|
const auto &viewport = mOutputLetterbox;
|
|
|
|
// Grab what is in the back buffer.
|
|
// We cannot rely on SCREENWIDTH/HEIGHT here because the output may have been scaled.
|
|
TArray<uint8_t> pixels;
|
|
pixels.Resize(viewport.width * viewport.height * 3);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
glReadPixels(viewport.left, viewport.top, viewport.width, viewport.height, GL_RGB, GL_UNSIGNED_BYTE, &pixels[0]);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
|
|
// Copy to screenshot buffer:
|
|
int w = SCREENWIDTH;
|
|
int h = SCREENHEIGHT;
|
|
|
|
TArray<uint8_t> ScreenshotBuffer(w * h * 3, true);
|
|
|
|
float rcpWidth = 1.0f / w;
|
|
float rcpHeight = 1.0f / h;
|
|
for (int y = 0; y < h; y++)
|
|
{
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
float u = (x + 0.5f) * rcpWidth;
|
|
float v = (y + 0.5f) * rcpHeight;
|
|
int sx = u * viewport.width;
|
|
int sy = v * viewport.height;
|
|
int sindex = (sx + sy * viewport.width) * 3;
|
|
int dindex = (x + (h - y - 1) * w) * 3;
|
|
ScreenshotBuffer[dindex] = pixels[sindex];
|
|
ScreenshotBuffer[dindex + 1] = pixels[sindex + 1];
|
|
ScreenshotBuffer[dindex + 2] = pixels[sindex + 2];
|
|
}
|
|
}
|
|
|
|
pitch = w * 3;
|
|
color_type = SS_RGB;
|
|
|
|
// Screenshot should not use gamma correction if it was already applied to rendered image
|
|
gamma = 1;
|
|
if (vid_hdr_active && vid_fullscreen)
|
|
gamma *= 2.2f;
|
|
return ScreenshotBuffer;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// 2D drawing
|
|
//
|
|
//===========================================================================
|
|
|
|
void OpenGLFrameBuffer::Draw2D()
|
|
{
|
|
if (GLRenderer != nullptr)
|
|
{
|
|
GLRenderer->mBuffers->BindCurrentFB();
|
|
::Draw2D(&m2DDrawer, gl_RenderState);
|
|
}
|
|
}
|
|
|
|
void OpenGLFrameBuffer::PostProcessScene(int fixedcm, const std::function<void()> &afterBloomDrawEndScene2D)
|
|
{
|
|
GLRenderer->PostProcessScene(fixedcm, afterBloomDrawEndScene2D);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// OpenGLFrameBuffer :: WipeStartScreen
|
|
//
|
|
// Called before the current screen has started rendering. This needs to
|
|
// save what was drawn the previous frame so that it can be animated into
|
|
// what gets drawn this frame.
|
|
//
|
|
//==========================================================================
|
|
|
|
FTexture *OpenGLFrameBuffer::WipeStartScreen()
|
|
{
|
|
const auto &viewport = screen->mScreenViewport;
|
|
|
|
auto tex = new FWrapperTexture(viewport.width, viewport.height, 1);
|
|
tex->GetSystemTexture()->CreateTexture(nullptr, viewport.width, viewport.height, 0, false, "WipeStartScreen");
|
|
glFinish();
|
|
static_cast<FHardwareTexture*>(tex->GetSystemTexture())->Bind(0, false);
|
|
|
|
GLRenderer->mBuffers->BindCurrentFB();
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
|
return tex;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// OpenGLFrameBuffer :: WipeEndScreen
|
|
//
|
|
// The screen we want to animate to has just been drawn.
|
|
//
|
|
//==========================================================================
|
|
|
|
FTexture *OpenGLFrameBuffer::WipeEndScreen()
|
|
{
|
|
GLRenderer->Flush();
|
|
const auto &viewport = screen->mScreenViewport;
|
|
auto tex = new FWrapperTexture(viewport.width, viewport.height, 1);
|
|
tex->GetSystemTexture()->CreateTexture(NULL, viewport.width, viewport.height, 0, false, "WipeEndScreen");
|
|
glFinish();
|
|
static_cast<FHardwareTexture*>(tex->GetSystemTexture())->Bind(0, false);
|
|
GLRenderer->mBuffers->BindCurrentFB();
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, viewport.left, viewport.top, viewport.width, viewport.height);
|
|
return tex;
|
|
}
|
|
|
|
}
|