Newer
Older
/**
* @file llrendertarget.cpp
* @brief LLRenderTarget implementation
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library 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;
* version 2.1 of the License only.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llrendertarget.h"
#include "llrender.h"
LLRenderTarget* LLRenderTarget::sBoundTarget = NULL;
U32 LLRenderTarget::sBytesAllocated = 0;
void check_framebuffer_status()
{
David Parks
committed
if (gDebugGL)
{
GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
switch (status)
{
case GL_FRAMEBUFFER_COMPLETE:
break;
default:
LL_WARNS() << "check_framebuffer_status failed -- " << std::hex << status << LL_ENDL;
ll_fail("check_framebuffer_status failed");
break;
}
}
}
David Parks
committed
bool LLRenderTarget::sUseFBO = false;
David Parks
committed
U32 LLRenderTarget::sCurFBO = 0;
extern S32 gGLViewport[4];
U32 LLRenderTarget::sCurResX = 0;
U32 LLRenderTarget::sCurResY = 0;
David Parks
committed
mResX(0),
mResY(0),
mFBO(0),
mDepth(0),
mUseDepth(false),
mUsage(LLTexUnit::TT_TEXTURE)
{
}
LLRenderTarget::~LLRenderTarget()
{
David Parks
committed
release();
}
Graham Linden
committed
void LLRenderTarget::resize(U32 resx, U32 resy)
David Parks
committed
//for accounting, get the number of pixels added/subtracted
S32 pix_diff = (resx*resy)-(mResX*mResY);
mResX = resx;
mResY = resy;
llassert(mInternalFormat.size() == mTex.size());
for (U32 i = 0; i < mTex.size(); ++i)
{ //resize color attachments
gGL.getTexUnit(0)->bindManual(mUsage, mTex[i]);
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, mInternalFormat[i], mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL, false);
sBytesAllocated += pix_diff*4;
}
if (mDepth)
{
gGL.getTexUnit(0)->bindManual(mUsage, mDepth);
U32 internal_type = LLTexUnit::getInternalType(mUsage);
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
sBytesAllocated += pix_diff*4;
}
David Parks
committed
bool LLRenderTarget::allocate(U32 resx, U32 resy, U32 color_fmt, bool depth, LLTexUnit::eTextureType usage, LLTexUnit::eTextureMipGeneration generateMipMaps)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
David Parks
committed
llassert(usage == LLTexUnit::TT_TEXTURE);
llassert(!isBoundInStack());
David Parks
committed
David Parks
committed
resx = llmin(resx, (U32) gGLManager.mGLMaxTextureSize);
resy = llmin(resy, (U32) gGLManager.mGLMaxTextureSize);
David Parks
committed
release();
mResX = resx;
mResY = resy;
David Parks
committed
mUsage = usage;
mUseDepth = depth;
mGenerateMipMaps = generateMipMaps;
if (mGenerateMipMaps != LLTexUnit::TMG_NONE) {
// Calculate the number of mip levels based upon resolution that we should have.
mMipLevels = 1 + floor(log10((float)llmax(mResX, mResY))/log10(2.0));
}
David Parks
committed
if (depth)
{
if (!allocateDepth())
{
LL_WARNS() << "Failed to allocate depth buffer for render target." << LL_ENDL;
return false;
}
}
glGenFramebuffers(1, (GLuint *) &mFBO);
if (mDepth)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
David Parks
committed
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, LLTexUnit::getInternalType(mUsage), mDepth, 0);
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
}
return addColorAttachment(color_fmt);
void LLRenderTarget::setColorAttachment(LLImageGL* img, LLGLuint use_name)
{
LL_PROFILE_ZONE_SCOPED;
llassert(img != nullptr); // img must not be null
llassert(sUseFBO); // FBO support must be enabled
llassert(mDepth == 0); // depth buffers not supported with this mode
llassert(mTex.empty()); // mTex must be empty with this mode (binding target should be done via LLImageGL)
David Parks
committed
llassert(!isBoundInStack());
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
if (mFBO == 0)
{
glGenFramebuffers(1, (GLuint*)&mFBO);
}
mResX = img->getWidth();
mResY = img->getHeight();
mUsage = img->getTarget();
if (use_name == 0)
{
use_name = img->getTexName();
}
mTex.push_back(use_name);
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
LLTexUnit::getInternalType(mUsage), use_name, 0);
stop_glerror();
check_framebuffer_status();
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
}
void LLRenderTarget::releaseColorAttachment()
{
LL_PROFILE_ZONE_SCOPED;
David Parks
committed
llassert(!isBoundInStack());
llassert(mTex.size() == 1); //cannot use releaseColorAttachment with LLRenderTarget managed color targets
llassert(mFBO != 0); // mFBO must be valid
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, LLTexUnit::getInternalType(mUsage), 0, 0);
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
mTex.clear();
}
bool LLRenderTarget::addColorAttachment(U32 color_fmt)
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
David Parks
committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
llassert(!isBoundInStack());
if (color_fmt == 0)
{
return true;
}
U32 offset = mTex.size();
if( offset >= 4 )
{
LL_WARNS() << "Too many color attachments" << LL_ENDL;
llassert( offset < 4 );
return false;
}
if( offset > 0 && (mFBO == 0) )
{
llassert( mFBO != 0 );
return false;
}
U32 tex;
LLImageGL::generateTextures(1, &tex);
gGL.getTexUnit(0)->bindManual(mUsage, tex);
stop_glerror();
{
clear_glerror();
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL, false);
if (glGetError() != GL_NO_ERROR)
{
LL_WARNS() << "Could not allocate color buffer for render target." << LL_ENDL;
return false;
}
}
sBytesAllocated += mResX*mResY*4;
stop_glerror();
if (offset == 0)
{ //use bilinear filtering on single texture render targets that aren't multisampled
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
stop_glerror();
}
else
{ //don't filter data attachments
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
stop_glerror();
}
if (mUsage != LLTexUnit::TT_RECT_TEXTURE)
{
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_MIRROR);
stop_glerror();
}
else
{
// ATI doesn't support mirrored repeat for rectangular textures.
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
stop_glerror();
}
if (mFBO)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+offset,
LLTexUnit::getInternalType(mUsage), tex, 0);
check_framebuffer_status();
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
}
mTex.push_back(tex);
mInternalFormat.push_back(color_fmt);
if (gDebugGL)
{ //bind and unbind to validate target
bindTarget();
flush();
}
David Parks
committed
return true;
}
bool LLRenderTarget::allocateDepth()
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
David Parks
committed
LLImageGL::generateTextures(1, &mDepth);
gGL.getTexUnit(0)->bindManual(mUsage, mDepth);
U32 internal_type = LLTexUnit::getInternalType(mUsage);
stop_glerror();
clear_glerror();
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
sBytesAllocated += mResX*mResY*4;
if (glGetError() != GL_NO_ERROR)
{
LL_WARNS() << "Unable to allocate depth buffer for render target." << LL_ENDL;
return false;
}
return true;
}
void LLRenderTarget::shareDepthBuffer(LLRenderTarget& target)
{
David Parks
committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
llassert(!isBoundInStack());
if (!mFBO || !target.mFBO)
{
LL_ERRS() << "Cannot share depth buffer between non FBO render targets." << LL_ENDL;
}
if (target.mDepth)
{
LL_ERRS() << "Attempting to override existing depth buffer. Detach existing buffer first." << LL_ENDL;
}
if (target.mUseDepth)
{
LL_ERRS() << "Attempting to override existing shared depth buffer. Detach existing buffer first." << LL_ENDL;
}
if (mDepth)
{
glBindFramebuffer(GL_FRAMEBUFFER, target.mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, LLTexUnit::getInternalType(mUsage), mDepth, 0);
check_framebuffer_status();
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
target.mUseDepth = true;
}
void LLRenderTarget::release()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
David Parks
committed
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
llassert(!isBoundInStack());
if (mDepth)
{
LLImageGL::deleteTextures(1, &mDepth);
mDepth = 0;
sBytesAllocated -= mResX*mResY*4;
}
else if (mFBO)
{
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
if (mUseDepth)
{ //detach shared depth buffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, LLTexUnit::getInternalType(mUsage), 0, 0);
mUseDepth = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
}
// Detach any extra color buffers (e.g. SRGB spec buffers)
//
if (mFBO && (mTex.size() > 1))
{
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
S32 z;
for (z = mTex.size() - 1; z >= 1; z--)
{
sBytesAllocated -= mResX*mResY*4;
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+z, LLTexUnit::getInternalType(mUsage), 0, 0);
LLImageGL::deleteTextures(1, &mTex[z]);
}
glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
}
if (mFBO)
{
if (mFBO == sCurFBO)
{
sCurFBO = 0;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
David Parks
committed
glDeleteFramebuffers(1, (GLuint *) &mFBO);
mFBO = 0;
}
if (mTex.size() > 0)
{
sBytesAllocated -= mResX*mResY*4;
LLImageGL::deleteTextures(1, &mTex[0]);
}
mTex.clear();
mInternalFormat.clear();
mResX = mResY = 0;
}
void LLRenderTarget::bindTarget()
{
LL_PROFILE_GPU_ZONE("bindTarget");
llassert(mFBO);
David Parks
committed
llassert(!isBoundInStack());
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
sCurFBO = mFBO;
//setup multiple render targets
GLenum drawbuffers[] = {GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3};
glDrawBuffers(mTex.size(), drawbuffers);
if (mTex.empty())
{ //no color buffer to draw to
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
}
David Parks
committed
check_framebuffer_status();
glViewport(0, 0, mResX, mResY);
sCurResX = mResX;
sCurResY = mResY;
mPreviousRT = sBoundTarget;
sBoundTarget = this;
}
void LLRenderTarget::clear(U32 mask_in)
{
LL_PROFILE_GPU_ZONE("clear");
llassert(mFBO);
David Parks
committed
U32 mask = GL_COLOR_BUFFER_BIT;
if (mUseDepth)
{
mask |= GL_DEPTH_BUFFER_BIT;
David Parks
committed
}
if (mFBO)
{
check_framebuffer_status();
stop_glerror();
glClear(mask & mask_in);
stop_glerror();
}
else
{
LLGLEnable scissor(GL_SCISSOR_TEST);
glScissor(0, 0, mResX, mResY);
stop_glerror();
glClear(mask & mask_in);
}
}
U32 LLRenderTarget::getTexture(U32 attachment) const
{
David Parks
committed
if (attachment > mTex.size()-1)
{
LL_ERRS() << "Invalid attachment index." << LL_ENDL;
}
if (mTex.empty())
{
return 0;
}
return mTex[attachment];
}
U32 LLRenderTarget::getNumTextures() const
{
David Parks
committed
return mTex.size();
}
void LLRenderTarget::bindTexture(U32 index, S32 channel, LLTexUnit::eTextureFilterOptions filter_options)
gGL.getTexUnit(channel)->bindManual(mUsage, getTexture(index), filter_options == LLTexUnit::TFO_TRILINEAR || filter_options == LLTexUnit::TFO_ANISOTROPIC);
gGL.getTexUnit(channel)->setTextureFilteringOption(filter_options);
}
David Parks
committed
void LLRenderTarget::flush()
{
LL_PROFILE_GPU_ZONE("rt flush");
David Parks
committed
gGL.flush();
llassert(mFBO);
David Parks
committed
llassert(sCurFBO == mFBO);
llassert(sBoundTarget == this);
if (mGenerateMipMaps == LLTexUnit::TMG_AUTO) {
LL_PROFILE_GPU_ZONE("rt generate mipmaps");
bindTexture(0, 0, LLTexUnit::TFO_TRILINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
}
David Parks
committed
if (mPreviousRT)
{
// a bit hacky -- pop the RT stack back two frames and push
// the previous frame back on to play nice with the GL state machine
sBoundTarget = mPreviousRT->mPreviousRT;
mPreviousRT->bindTarget();
}
else
{
sBoundTarget = nullptr;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
sCurFBO = 0;
glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]);
sCurResX = gGLViewport[2];
sCurResY = gGLViewport[3];
}
}
David Parks
committed
bool LLRenderTarget::isComplete() const
{
David Parks
committed
return (!mTex.empty() || mDepth) ? true : false;
}
void LLRenderTarget::getViewport(S32* viewport)
{
David Parks
committed
viewport[0] = 0;
viewport[1] = 0;
viewport[2] = mResX;
viewport[3] = mResY;
}
David Parks
committed
bool LLRenderTarget::isBoundInStack() const
{
LLRenderTarget* cur = sBoundTarget;
while (cur && cur != this)
{
cur = cur->mPreviousRT;
}
return cur == this;
}
Nyx Linden
committed
Cosmic Linden
committed
void LLRenderTarget::swapFBORefs(LLRenderTarget& other)
{
// Must be initialized
llassert(mFBO);
llassert(other.mFBO);
Nyx Linden
committed
Cosmic Linden
committed
// Must be unbound
// *NOTE: mPreviousRT can be non-null even if this target is unbound - presumably for debugging purposes?
llassert(sCurFBO != mFBO);
llassert(sCurFBO != other.mFBO);
llassert(!isBoundInStack());
llassert(!other.isBoundInStack());
// Must be same type
llassert(sUseFBO == other.sUseFBO);
llassert(mResX == other.mResX);
llassert(mResY == other.mResY);
llassert(mInternalFormat == other.mInternalFormat);
llassert(mTex.size() == other.mTex.size());
llassert(mDepth == other.mDepth);
llassert(mUseDepth == other.mUseDepth);
llassert(mGenerateMipMaps == other.mGenerateMipMaps);
llassert(mMipLevels == other.mMipLevels);
llassert(mUsage == other.mUsage);
std::swap(mFBO, other.mFBO);
std::swap(mTex, other.mTex);