// // Urho3D Engine // Copyright (c) 2008-2012 Lasse Öörni // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "Precompiled.h" #include "AnimatedModel.h" #include "Animation.h" #include "AnimationController.h" #include "BillboardSet.h" #include "Camera.h" #include "Context.h" #include "DebugRenderer.h" #include "Graphics.h" #include "GraphicsEvents.h" #include "GraphicsImpl.h" #include "IndexBuffer.h" #include "Light.h" #include "Log.h" #include "Mutex.h" #include "Octree.h" #include "ParticleEmitter.h" #include "ProcessUtils.h" #include "Profiler.h" #include "RenderSurface.h" #include "Shader.h" #include "ShaderProgram.h" #include "ShaderVariation.h" #include "Skybox.h" #include "StringUtils.h" #include "Technique.h" #include "Texture2D.h" #include "TextureCube.h" #include "VertexBuffer.h" #include "Zone.h" #include #include "DebugNew.h" #ifdef GL_ES_VERSION_2_0 #define GL_DEPTH_COMPONENT24 GL_DEPTH_COMPONENT24_OES #define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER #define GL_RENDERBUFFER_EXT GL_RENDERBUFFER #define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0 #define GL_DEPTH_ATTACHMENT_EXT GL_DEPTH_ATTACHMENT #define GL_STENCIL_ATTACHMENT_EXT GL_STENCIL_ATTACHMENT #define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE #define glClearDepth glClearDepthf #define glBindFramebufferEXT glBindFramebuffer #define glFramebufferTexture2DEXT glFramebufferTexture2D #define glFramebufferRenderbufferEXT glFramebufferRenderbuffer #define glGenFramebuffersEXT glGenFramebuffers #define glDeleteFramebuffersEXT glDeleteFramebuffers #define glCheckFramebufferStatusEXT glCheckFramebufferStatus #endif static const unsigned glCmpFunc[] = { GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL }; static const unsigned glSrcBlend[] = { GL_ONE, GL_ONE, GL_DST_COLOR, GL_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_DST_ALPHA }; static const unsigned glDestBlend[] = { GL_ZERO, GL_ONE, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA }; static const unsigned glStencilOps[] = { GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR_WRAP, GL_DECR_WRAP }; static unsigned numInstances = 0; static const String noParameter; static const unsigned MAX_FRAMEBUFFER_AGE = 2000; OBJECTTYPESTATIC(Graphics); bool CheckExtension(const String& name) { String extensions((const char*)glGetString(GL_EXTENSIONS)); return extensions.Find(name) != String::NPOS; } Graphics::Graphics(Context* context_) : Object(context_), impl_(new GraphicsImpl()), width_(0), height_(0), multiSample_(1), fullscreen_(false), vsync_(false), tripleBuffer_(false), lightPrepassSupport_(false), deferredSupport_(false), hardwareDepthSupport_(false), compressedTextureSupport_(false), numPrimitives_(0), numBatches_(0), defaultTextureFilterMode_(FILTER_BILINEAR), shadowMapFormat_(GL_DEPTH_COMPONENT16), hiresShadowMapFormat_(GL_DEPTH_COMPONENT24), shaderParameterFrame_(0) { ResetCachedState(); SetTextureUnitMappings(); // If first instance in this process, initialize SDL under static mutex. Note that Graphics subsystem will also be in charge // of shutting down SDL as a whole, so it should be the last SDL-using subsystem (Audio and Input also use SDL) alive { MutexLock lock(GetStaticMutex()); if (!numInstances) SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE); ++numInstances; } } Graphics::~Graphics() { Close(); delete impl_; impl_ = 0; // If last instance in this process, shut down SDL under static mutex { MutexLock lock(GetStaticMutex()); --numInstances; if (!numInstances) SDL_Quit(); } } void Graphics::SetWindowTitle(const String& windowTitle) { windowTitle_ = windowTitle; if (impl_->window_) SDL_SetWindowTitle(impl_->window_, windowTitle_.CString()); } bool Graphics::SetMode(int width, int height, bool fullscreen, bool vsync, bool tripleBuffer, int multiSample) { PROFILE(SetScreenMode); multiSample = Clamp(multiSample, 1, 16); if (IsInitialized() && width == width_ && height == height_ && fullscreen == fullscreen_ && vsync == vsync_ && tripleBuffer == tripleBuffer_ && multiSample == multiSample_) return true; // If only vsync changes, do not destroy/recreate the context if (IsInitialized() && width == width_ && height == height_ && fullscreen == fullscreen_ && tripleBuffer == tripleBuffer_ && multiSample == multiSample_ && vsync != vsync_) { SDL_GL_SetSwapInterval(vsync ? 1 : 0); vsync_ = vsync; return true; } // If zero dimensions in windowed mode, set default. If zero in fullscreen, use desktop mode if (!width || !height) { if (!fullscreen) { width = 800; height = 600; } else { SDL_DisplayMode mode; SDL_GetDesktopDisplayMode(0, &mode); width = mode.w; height = mode.h; } } // Close the existing window and OpenGL context, mark GPU objects as lost Release(false, true); { // SDL window parameters are static, so need to operate under static lock MutexLock lock(GetStaticMutex()); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); if (multiSample > 1) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multiSample); } else { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); } unsigned flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; int x = fullscreen ? 0 : SDL_WINDOWPOS_UNDEFINED; int y = fullscreen ? 0 : SDL_WINDOWPOS_UNDEFINED; if (fullscreen) flags |= SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS; impl_->window_ = SDL_CreateWindow(windowTitle_.CString(), x, y, width, height, flags); if (!impl_->window_) { LOGERROR("Could not open window"); return false; } impl_->context_ = SDL_GL_CreateContext(impl_->window_); if (!impl_->context_) { LOGERROR("Could not create OpenGL context"); return false; } // If OpenGL extensions not yet initialized, initialize now #ifndef GL_ES_VERSION_2_0 if (!GLeeInitialized()) GLeeInit(); if (!_GLEE_VERSION_2_0) { LOGERROR("OpenGL 2.0 is required"); Release(true, true); return false; } if (!CheckExtension("EXT_framebuffer_object") || !CheckExtension("EXT_packed_depth_stencil") || !CheckExtension("EXT_texture_filter_anisotropic")) { LOGERROR("EXT_framebuffer_object, EXT_packed_depth_stencil and " "EXT_texture_filter_anisotropic OpenGL extensions are required"); Release(true, true); return false; } #endif compressedTextureSupport_ = CheckExtension("EXT_texture_compression_s3tc"); } // Set vsync SDL_GL_SetSwapInterval(vsync ? 1 : 0); // Query for system backbuffer depth glGetIntegerv(GL_DEPTH_BITS, &impl_->windowDepthBits_); impl_->depthBits_ = impl_->windowDepthBits_; // Set initial state to match Direct3D glEnable(GL_DEPTH_TEST); SetCullMode(CULL_CCW); SetDepthTest(CMP_LESSEQUAL); SDL_GetWindowSize(impl_->window_, &width_, &height_); fullscreen_ = fullscreen; vsync_ = vsync; tripleBuffer_ = tripleBuffer; multiSample_ = multiSample; // Reset rendertargets and viewport for the new screen mode ResetRenderTargets(); // Clear the window to black now, because GPU object restore may take time Clear(CLEAR_COLOR); SDL_GL_SwapWindow(impl_->window_); // Let GPU objects restore themselves for (Vector::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i) (*i)->OnDeviceReset(); CheckFeatureSupport(); if (multiSample > 1) LOGINFO("Set screen mode " + String(width_) + "x" + String(height_) + " " + (fullscreen_ ? "fullscreen" : "windowed") + " multisample " + String(multiSample)); else LOGINFO("Set screen mode " + String(width_) + "x" + String(height_) + " " + (fullscreen_ ? "fullscreen" : "windowed")); using namespace ScreenMode; VariantMap eventData; eventData[P_WIDTH] = width_; eventData[P_HEIGHT] = height_; eventData[P_FULLSCREEN] = fullscreen_; SendEvent(E_SCREENMODE, eventData); return true; } bool Graphics::SetMode(int width, int height) { return SetMode(width, height, fullscreen_, vsync_, tripleBuffer_, multiSample_); } bool Graphics::ToggleFullscreen() { return SetMode(width_, height_, !fullscreen_, vsync_, tripleBuffer_, multiSample_); } void Graphics::Close() { if (!IsInitialized()) return; // Actually close the window Release(true, true); } bool Graphics::TakeScreenShot(Image& destImage) { PROFILE(TakeScreenShot); ResetRenderTargets(); destImage.SetSize(width_, height_, 3); glReadPixels(0, 0, width_, height_, GL_RGB, GL_UNSIGNED_BYTE, destImage.GetData()); return true; } bool Graphics::BeginFrame() { if (!IsInitialized()) return false; // If we should be fullscreen, but are not currently active, do not render if (fullscreen_ && (SDL_GetWindowFlags(impl_->window_) & SDL_WINDOW_MINIMIZED)) return false; // Set default rendertarget and depth buffer ResetRenderTargets(); // Cleanup textures from previous frame for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i) SetTexture(i, 0); // Enable color and depth write SetColorWrite(true); SetDepthWrite(true); numPrimitives_ = 0; numBatches_ = 0; SendEvent(E_BEGINRENDERING); return true; } void Graphics::EndFrame() { if (!IsInitialized()) return; PROFILE(Present); SendEvent(E_ENDRENDERING); SDL_GL_SwapWindow(impl_->window_); // Clean up FBO's that have not been used for a long time CleanupFramebuffers(false); } void Graphics::Clear(unsigned flags, const Color& color, float depth, unsigned stencil) { if (impl_->fboDirty_) CommitFramebuffer(); bool oldColorWrite = colorWrite_; bool oldDepthWrite = depthWrite_; if (flags & CLEAR_COLOR && !oldColorWrite) SetColorWrite(true); if (flags & CLEAR_DEPTH && !oldDepthWrite) SetDepthWrite(true); if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_MAX_UNSIGNED) glStencilMask(M_MAX_UNSIGNED); unsigned glFlags = 0; if (flags & CLEAR_COLOR) { glFlags |= GL_COLOR_BUFFER_BIT; glClearColor(color.r_, color.g_, color.b_, color.a_); } if (flags & CLEAR_DEPTH) { glFlags |= GL_DEPTH_BUFFER_BIT; glClearDepth(depth); } if (flags & CLEAR_STENCIL) { glFlags |= GL_STENCIL_BUFFER_BIT; glClearStencil(stencil); } // If viewport is less than full screen, set a scissor to limit the clear /// \todo Any user-set scissor test will be lost IntVector2 viewSize = GetRenderTargetDimensions(); if (viewport_.left_ != 0 || viewport_.top_ != 0 || viewport_.right_ != viewSize.x_ || viewport_.bottom_ != viewSize.y_) SetScissorTest(true, IntRect(0, 0, viewport_.right_ - viewport_.left_, viewport_.bottom_ - viewport_.top_)); else SetScissorTest(false); glClear(glFlags); SetScissorTest(false); SetColorWrite(oldColorWrite); SetDepthWrite(oldDepthWrite); if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_MAX_UNSIGNED) glStencilMask(stencilWriteMask_); } bool Graphics::ResolveToTexture(Texture2D* destination, const IntRect& viewport) { if (!destination || !destination->GetRenderSurface() || destination->GetWidth() != width_ || destination->GetHeight() != height_) return false; IntRect vpCopy = viewport; if (vpCopy.right_ <= vpCopy.left_) vpCopy.right_ = vpCopy.left_ + 1; if (vpCopy.bottom_ <= vpCopy.top_) vpCopy.bottom_ = vpCopy.top_ + 1; vpCopy.left_ = Clamp(vpCopy.left_, 0, width_); vpCopy.top_ = Clamp(vpCopy.top_, 0, height_); vpCopy.right_ = Clamp(vpCopy.right_, 0, width_); vpCopy.bottom_ = Clamp(vpCopy.bottom_, 0, height_); // Make sure the FBO is not in use ResetRenderTargets(); // Use Direct3D convention with the vertical coordinates ie. 0 is top SetTextureForUpdate(destination); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, vpCopy.left_, height_ - vpCopy.bottom_, vpCopy.left_, height_ - vpCopy.bottom_, vpCopy.right_ - vpCopy.left_, vpCopy.bottom_ - vpCopy.top_); SetTexture(0, 0); return true; } void Graphics::Draw(PrimitiveType type, unsigned vertexStart, unsigned vertexCount) { if (!vertexCount) return; if (impl_->fboDirty_) CommitFramebuffer(); unsigned primitiveCount = 0; switch (type) { case TRIANGLE_LIST: primitiveCount = vertexCount / 3; glDrawArrays(GL_TRIANGLES, vertexStart, vertexCount); break; case LINE_LIST: primitiveCount = vertexCount / 2; glDrawArrays(GL_LINES, vertexStart, vertexCount); break; } numPrimitives_ += primitiveCount; ++numBatches_; } void Graphics::Draw(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount) { if (!indexCount || !indexBuffer_) return; if (impl_->fboDirty_) CommitFramebuffer(); unsigned primitiveCount = 0; unsigned indexSize = indexBuffer_->GetIndexSize(); switch (type) { case TRIANGLE_LIST: primitiveCount = indexCount / 3; if (indexSize == sizeof(unsigned short)) glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, (const GLvoid*)(indexStart * indexSize)); else glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (const GLvoid*)(indexStart * indexSize)); break; case LINE_LIST: primitiveCount = indexCount / 2; if (indexSize == sizeof(unsigned short)) glDrawElements(GL_LINES, indexCount, GL_UNSIGNED_SHORT, (const GLvoid*)(indexStart * indexSize)); else glDrawElements(GL_LINES, indexCount, GL_UNSIGNED_INT, (const GLvoid*)(indexStart * indexSize)); break; } numPrimitives_ += primitiveCount; ++numBatches_; } void Graphics::DrawInstanced(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount, unsigned instanceCount) { } void Graphics::SetVertexBuffer(VertexBuffer* buffer) { Vector vertexBuffers(1); PODVector elementMasks(1); vertexBuffers[0] = buffer; elementMasks[0] = MASK_DEFAULT; SetVertexBuffers(vertexBuffers, elementMasks); } bool Graphics::SetVertexBuffers(const Vector& buffers, const PODVector& elementMasks, unsigned instanceOffset) { if (buffers.Size() > MAX_VERTEX_STREAMS) { LOGERROR("Too many vertex buffers"); return false; } if (buffers.Size() != elementMasks.Size()) { LOGERROR("Amount of element masks and vertex buffers does not match"); return false; } // If no valid shader to determine the attribute bindings, can not set vertex buffers if (!shaderProgram_) return false; const int* attributeLocations = shaderProgram_->GetAttributeLocations(); bool changed = false; unsigned newAttributes = 0; for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) { VertexBuffer* buffer = 0; unsigned elementMask = 0; if (i < buffers.Size()) { buffer = buffers[i]; elementMask = elementMasks[i]; if (elementMask == MASK_DEFAULT && buffer) elementMask = buffers[i]->GetElementMask(); } // If buffer and element mask have stayed the same, skip to the next buffer if (buffer == vertexBuffers_[i] && elementMask == elementMasks_[i]) continue; vertexBuffers_[i] = buffer; elementMasks_[i] = elementMask; changed = true; if (!buffer) continue; glBindBuffer(GL_ARRAY_BUFFER, buffer->GetGPUObject()); unsigned vertexSize = buffer->GetVertexSize(); for (unsigned j = 0; j < MAX_VERTEX_ELEMENTS; ++j) { // If shader does not use the attribute, do not bind it (bandwidth optimization) int attributeIndex = attributeLocations[j]; if (attributeIndex < 0) continue; unsigned elementBit = 1 << j; unsigned attributeBit = 1 << attributeIndex; if (elementMask & elementBit) { newAttributes |= attributeBit; // Enable attribute if not enabled yet if ((impl_->enabledAttributes_ & attributeBit) == 0) { glEnableVertexAttribArray(attributeIndex); impl_->enabledAttributes_ |= attributeBit; } // Set the attribute pointer glVertexAttribPointer(attributeIndex, VertexBuffer::elementComponents[j], VertexBuffer::elementType[j], VertexBuffer::elementNormalize[j], vertexSize, (const GLvoid*)(buffer->GetElementOffset((VertexElement)j))); } } } if (!changed) return true; // Now check which vertex attributes should be disabled unsigned disableAttributes = impl_->enabledAttributes_ & (~newAttributes); int disableIndex = 0; while (disableAttributes) { if (disableAttributes & 1) { glDisableVertexAttribArray(disableIndex); impl_->enabledAttributes_ &= ~(1 << disableIndex); } disableAttributes >>= 1; ++disableIndex; } return true; } bool Graphics::SetVertexBuffers(const Vector >& buffers, const PODVector& elementMasks, unsigned instanceOffset) { if (buffers.Size() > MAX_VERTEX_STREAMS) { LOGERROR("Too many vertex buffers"); return false; } if (buffers.Size() != elementMasks.Size()) { LOGERROR("Amount of element masks and vertex buffers does not match"); return false; } // If no valid shader to determine the attribute bindings, can not set vertex buffers if (!shaderProgram_) return false; const int* attributeLocations = shaderProgram_->GetAttributeLocations(); bool changed = false; unsigned newAttributes = 0; for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) { VertexBuffer* buffer = 0; unsigned elementMask = 0; if (i < buffers.Size()) { buffer = buffers[i]; elementMask = elementMasks[i]; if (elementMask == MASK_DEFAULT && buffer) elementMask = buffers[i]->GetElementMask(); } // If buffer and element mask have stayed the same, skip to the next buffer if (buffer == vertexBuffers_[i] && elementMask == elementMasks_[i]) continue; vertexBuffers_[i] = buffer; elementMasks_[i] = elementMask; changed = true; if (!buffer) continue; glBindBuffer(GL_ARRAY_BUFFER, buffer->GetGPUObject()); unsigned vertexSize = buffer->GetVertexSize(); for (unsigned j = 0; j < MAX_VERTEX_ELEMENTS; ++j) { // If shader does not use the attribute, do not bind it (bandwidth optimization) int attributeIndex = attributeLocations[j]; if (attributeIndex < 0) continue; unsigned elementBit = 1 << j; unsigned attributeBit = 1 << attributeIndex; if (elementMask & elementBit) { newAttributes |= attributeBit; // Enable attribute if not enabled yet if ((impl_->enabledAttributes_ & attributeBit) == 0) { glEnableVertexAttribArray(attributeIndex); impl_->enabledAttributes_ |= attributeBit; } // Set the attribute pointer glVertexAttribPointer(attributeIndex, VertexBuffer::elementComponents[j], VertexBuffer::elementType[j], VertexBuffer::elementNormalize[j], vertexSize, (const GLvoid*)(buffer->GetElementOffset((VertexElement)j))); } } } if (!changed) return true; // Now check which vertex attributes should be disabled unsigned disableAttributes = impl_->enabledAttributes_ & (~newAttributes); int disableIndex = 0; while (disableAttributes) { if (disableAttributes & 1) { glDisableVertexAttribArray(disableIndex); impl_->enabledAttributes_ &= ~(1 << disableIndex); } disableAttributes >>= 1; ++disableIndex; } return true; } void Graphics::SetIndexBuffer(IndexBuffer* buffer) { if (indexBuffer_ == buffer) return; if (buffer) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->GetGPUObject()); else glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); indexBuffer_ = buffer; } void Graphics::SetShaders(ShaderVariation* vs, ShaderVariation* ps) { if (vs == vertexShader_ && ps == pixelShader_) return; ClearParameterSources(); // Compile the shaders now if not yet compiled. If already attempted, do not retry if (vs && !vs->IsCompiled()) { if (vs->GetCompilerOutput().Empty()) { PROFILE(CompileVertexShader); bool success = vs->Create(); if (success) LOGDEBUG("Compiled vertex shader " + vs->GetName()); else { LOGERROR("Failed to compile vertex shader " + vs->GetName() + ":\n" + vs->GetCompilerOutput()); vs = 0; } } else vs = 0; } if (ps && !ps->IsCompiled()) { if (ps->GetCompilerOutput().Empty()) { PROFILE(CompilePixelShader); bool success = ps->Create(); if (success) LOGDEBUG("Compiled pixel shader " + ps->GetName()); else { LOGERROR("Failed to compile pixel shader " + ps->GetName() + ":\n" + ps->GetCompilerOutput()); ps = 0; } } else ps = 0; } for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) { vertexBuffers_[i] = 0; elementMasks_[i] = 0; } if (!vs || !ps) { glUseProgram(0); vertexShader_ = 0; pixelShader_ = 0; shaderProgram_ = 0; } else { vertexShader_ = vs; pixelShader_ = ps; Pair combination(vs, ps); ShaderProgramMap::Iterator i = shaderPrograms_.Find(combination); if (i != shaderPrograms_.End()) { // Use the existing linked program if (i->second_->IsLinked()) { glUseProgram(i->second_->GetGPUObject()); shaderProgram_ = i->second_; } else { glUseProgram(0); shaderProgram_ = 0; } } else { // Link a new combination SharedPtr newProgram(new ShaderProgram(this, vs, ps)); if (newProgram->Link()) { LOGDEBUG("Linked vertex shader " + vs->GetName() + " and pixel shader " + ps->GetName()); // Note: Link() calls glUseProgram() to set the texture sampler uniforms, // so it is not necessary to call it again shaderProgram_ = newProgram; } else { LOGERROR("Failed to link vertex shader " + vs->GetName() + " and pixel shader " + ps->GetName() + ":\n" + newProgram->GetLinkerOutput()); glUseProgram(0); shaderProgram_ = 0; } shaderPrograms_[combination] = newProgram; } } } void Graphics::SetShaderParameter(StringHash param, const float* data, unsigned count) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) { switch (info->type_) { case GL_FLOAT: glUniform1fv(info->location_, count, data); break; case GL_FLOAT_VEC2: glUniform2fv(info->location_, count / 2, data); break; case GL_FLOAT_VEC3: glUniform3fv(info->location_, count / 3, data); break; case GL_FLOAT_VEC4: glUniform4fv(info->location_, count / 4, data); break; case GL_FLOAT_MAT3: glUniformMatrix3fv(info->location_, count / 9, GL_TRUE, data); break; case GL_FLOAT_MAT4: glUniformMatrix4fv(info->location_, count / 16, GL_TRUE, data); break; } } } } void Graphics::SetShaderParameter(StringHash param, float value) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) glUniform1fv(info->location_, 1, &value); } } void Graphics::SetShaderParameter(StringHash param, const Color& color) { SetShaderParameter(param, color.Data(), 4); } void Graphics::SetShaderParameter(StringHash param, const Matrix3& matrix) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) glUniformMatrix3fv(info->location_, 1, GL_TRUE, matrix.Data()); } } void Graphics::SetShaderParameter(StringHash param, const Vector3& vector) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) { // Check the uniform type to avoid mismatch switch (info->type_) { case GL_FLOAT: glUniform1fv(info->location_, 1, vector.Data()); break; case GL_FLOAT_VEC2: glUniform2fv(info->location_, 1, vector.Data()); break; case GL_FLOAT_VEC3: glUniform3fv(info->location_, 1, vector.Data()); break; } } } } void Graphics::SetShaderParameter(StringHash param, const Matrix4& matrix) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) glUniformMatrix4fv(info->location_, 1, GL_TRUE, matrix.Data()); } } void Graphics::SetShaderParameter(StringHash param, const Vector4& vector) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) { // Check the uniform type to avoid mismatch switch (info->type_) { case GL_FLOAT: glUniform1fv(info->location_, 1, vector.Data()); break; case GL_FLOAT_VEC2: glUniform2fv(info->location_, 1, vector.Data()); break; case GL_FLOAT_VEC3: glUniform3fv(info->location_, 1, vector.Data()); break; case GL_FLOAT_VEC4: glUniform4fv(info->location_, 1, vector.Data()); break; } } } } void Graphics::SetShaderParameter(StringHash param, const Matrix3x4& matrix) { if (shaderProgram_) { const ShaderParameter* info = shaderProgram_->GetParameter(param); if (info) { float data[16]; data[0] = matrix.m00_; data[1] = matrix.m01_; data[2] = matrix.m02_; data[3] = matrix.m03_; data[4] = matrix.m10_; data[5] = matrix.m11_; data[6] = matrix.m12_; data[7] = matrix.m13_; data[8] = matrix.m20_; data[9] = matrix.m21_; data[10] = matrix.m22_; data[11] = matrix.m23_; data[12] = 0.0f; data[13] = 0.0f; data[14] = 0.0f; data[15] = 1.0f; glUniformMatrix4fv(info->location_, 1, GL_TRUE, data); } } } bool Graphics::NeedParameterUpdate(ShaderParameterGroup group, const void* source) { if ((unsigned)shaderParameterSources_[group] == M_MAX_UNSIGNED || shaderParameterSources_[group] != source) { shaderParameterSources_[group] = source; return true; } else return false; } bool Graphics::HasShaderParameter(ShaderType type, StringHash param) { return shaderProgram_ && shaderProgram_->HasParameter(param); } bool Graphics::HasTextureUnit(TextureUnit unit) { return shaderProgram_ && shaderProgram_->HasTextureUnit(unit); } void Graphics::ClearParameterSource(ShaderParameterGroup group) { shaderParameterSources_[group] = (const void*)M_MAX_UNSIGNED; } void Graphics::ClearParameterSources() { for (unsigned i = 0; i < MAX_SHADER_PARAMETER_GROUPS; ++i) shaderParameterSources_[i] = (const void*)M_MAX_UNSIGNED; } void Graphics::ClearTransformSources() { shaderParameterSources_[SP_CAMERA] = (const void*)M_MAX_UNSIGNED; shaderParameterSources_[SP_OBJECTTRANSFORM] = (const void*)M_MAX_UNSIGNED; } void Graphics::CleanupShaderPrograms() { for (ShaderProgramMap::Iterator i = shaderPrograms_.Begin(); i != shaderPrograms_.End();) { ShaderProgramMap::Iterator current = i++; ShaderVariation* vs = current->second_->GetVertexShader(); ShaderVariation* ps = current->second_->GetPixelShader(); if (!vs || !ps || !vs->GetGPUObject() || !ps->GetGPUObject()) shaderPrograms_.Erase(current); } } void Graphics::SetTexture(unsigned index, Texture* texture) { if (index >= MAX_TEXTURE_UNITS) return; // Check if texture is currently bound as a rendertarget. In that case, use its backup texture, or blank if not defined if (texture) { if (texture == viewTexture_ || (renderTargets_[0] && renderTargets_[0]->GetParentTexture() == texture)) texture = texture->GetBackupTexture(); } if (textures_[index] != texture) { if (impl_->activeTexture_ != index) { glActiveTexture(GL_TEXTURE0 + index); impl_->activeTexture_ = index; } if (texture) { unsigned glType = texture->GetTarget(); if (glType != textureTypes_[index]) { if (textureTypes_[index]) glDisable(textureTypes_[index]); glEnable(glType); textureTypes_[index] = glType; } glBindTexture(glType, texture->GetGPUObject()); if (texture->GetParametersDirty()) texture->UpdateParameters(); } else { if (textureTypes_[index]) glBindTexture(textureTypes_[index], 0); } textures_[index] = texture; } else { if (texture && texture->GetParametersDirty()) { if (impl_->activeTexture_ != index) { glActiveTexture(GL_TEXTURE0 + index); impl_->activeTexture_ = index; } glBindTexture(texture->GetTarget(), texture->GetGPUObject()); texture->UpdateParameters(); } } } void Graphics::SetTextureForUpdate(Texture* texture) { if (impl_->activeTexture_ != 0) { glActiveTexture(GL_TEXTURE0); impl_->activeTexture_ = 0; } glBindTexture(texture->GetTarget(), texture->GetGPUObject()); textures_[0] = texture; } void Graphics::SetDefaultTextureFilterMode(TextureFilterMode mode) { if (mode != defaultTextureFilterMode_) { defaultTextureFilterMode_ = mode; SetTextureParametersDirty(); } } void Graphics::SetTextureAnisotropy(unsigned level) { if (level != textureAnisotropy_) { textureAnisotropy_ = level; SetTextureParametersDirty(); } } void Graphics::SetTextureParametersDirty() { for (Vector::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i) { Texture* texture = dynamic_cast(*i); if (texture) texture->SetParametersDirty(); } } void Graphics::ResetRenderTargets() { for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i) SetRenderTarget(i, (RenderSurface*)0); SetDepthStencil((RenderSurface*)0); SetViewport(IntRect(0, 0, width_, height_)); } void Graphics::ResetRenderTarget(unsigned index) { SetRenderTarget(index, (RenderSurface*)0); } void Graphics::ResetDepthStencil() { SetDepthStencil((RenderSurface*)0); } void Graphics::SetRenderTarget(unsigned index, RenderSurface* renderTarget) { if (index >= MAX_RENDERTARGETS) return; if (renderTarget != renderTargets_[index]) { renderTargets_[index] = renderTarget; // If the rendertarget is also bound as a texture, replace with backup texture or null if (renderTarget) { Texture* parentTexture = renderTarget->GetParentTexture(); for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i) { if (textures_[i] == parentTexture) SetTexture(i, textures_[i]->GetBackupTexture()); } } impl_->fboDirty_ = true; } } void Graphics::SetRenderTarget(unsigned index, Texture2D* texture) { RenderSurface* renderTarget = 0; if (texture) renderTarget = texture->GetRenderSurface(); SetRenderTarget(index, renderTarget); } void Graphics::SetDepthStencil(RenderSurface* depthStencil) { // If we are using a rendertarget texture, it is required in OpenGL to also have an own depth-stencil // Create a new depth-stencil texture as necessary to be able to provide similar behaviour as Direct3D9 if (renderTargets_[0] && !depthStencil) { int width = renderTargets_[0]->GetWidth(); int height = renderTargets_[0]->GetHeight(); // Direct3D9 default depth-stencil can not be used when rendertarget is larger than the window. // Check size similarly if (width <= width_ && height <= height_) { int searchKey = (width << 16) | height; HashMap >::Iterator i = depthTextures_.Find(searchKey); if (i != depthTextures_.End()) depthStencil = i->second_->GetRenderSurface(); else { SharedPtr newDepthTexture(new Texture2D(context_)); newDepthTexture->SetSize(width, height, GetDepthStencilFormat(), TEXTURE_DEPTHSTENCIL); depthTextures_[searchKey] = newDepthTexture; depthStencil = newDepthTexture->GetRenderSurface(); } } } if (depthStencil != depthStencil_) { depthStencil_ = depthStencil; impl_->fboDirty_ = true; } } void Graphics::SetDepthStencil(Texture2D* texture) { RenderSurface* depthStencil = 0; if (texture) depthStencil = texture->GetRenderSurface(); SetDepthStencil(depthStencil); } void Graphics::SetViewTexture(Texture* texture) { viewTexture_ = texture; if (viewTexture_) { for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i) { if (textures_[i] == viewTexture_) SetTexture(i, textures_[i]->GetBackupTexture()); } } } void Graphics::SetViewport(const IntRect& rect) { if (impl_->fboDirty_) CommitFramebuffer(); IntVector2 rtSize = GetRenderTargetDimensions(); IntRect rectCopy = rect; if (rectCopy.right_ <= rectCopy.left_) rectCopy.right_ = rectCopy.left_ + 1; if (rectCopy.bottom_ <= rectCopy.top_) rectCopy.bottom_ = rectCopy.top_ + 1; rectCopy.left_ = Clamp(rectCopy.left_, 0, rtSize.x_); rectCopy.top_ = Clamp(rectCopy.top_, 0, rtSize.y_); rectCopy.right_ = Clamp(rectCopy.right_, 0, rtSize.x_); rectCopy.bottom_ = Clamp(rectCopy.bottom_, 0, rtSize.y_); // Use Direct3D convention with the vertical coordinates ie. 0 is top glViewport(rectCopy.left_, rtSize.y_ - rectCopy.bottom_, rectCopy.right_ - rectCopy.left_, rectCopy.bottom_ - rectCopy.top_); viewport_ = rectCopy; // Disable scissor test, needs to be re-enabled by the user SetScissorTest(false); } void Graphics::SetBlendMode(BlendMode mode) { if (mode != blendMode_) { if (mode == BLEND_REPLACE) glDisable(GL_BLEND); else { glEnable(GL_BLEND); glBlendFunc(glSrcBlend[mode], glDestBlend[mode]); } blendMode_ = mode; } } void Graphics::SetColorWrite(bool enable) { if (enable != colorWrite_) { if (enable) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); else glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); colorWrite_ = enable; } } void Graphics::SetCullMode(CullMode mode) { if (mode != cullMode_) { if (mode == CULL_NONE) glDisable(GL_CULL_FACE); else { // Use Direct3D convention, ie. clockwise vertices define a front face glEnable(GL_CULL_FACE); glCullFace(mode == CULL_CCW ? GL_FRONT : GL_BACK); } cullMode_ = mode; } } void Graphics::SetDepthBias(float constantBias, float slopeScaledBias) { if (constantBias != constantDepthBias_ || slopeScaledBias != slopeScaledDepthBias_) { if (constantBias != 0.0f || slopeScaledBias != 0.0f) { // Bring the constant bias from Direct3D9 scale to OpenGL (depends on depth buffer bitdepth) // Zero depth bits may be returned if using the packed depth-stencil format. Assume 24bit in that case int depthBits = Min(impl_->depthBits_, 23); if (!depthBits) depthBits = 23; float adjustedConstantBias = constantBias * (float)(1 << (depthBits - 1)); float adjustedSlopeScaledBias = slopeScaledBias + 1.0f; glEnable(GL_POLYGON_OFFSET_FILL); #ifndef GL_ES_VERSION_2_0 glEnable(GL_POLYGON_OFFSET_LINE); #endif glPolygonOffset(adjustedSlopeScaledBias, adjustedConstantBias); } else { glDisable(GL_POLYGON_OFFSET_FILL); #ifndef GL_ES_VERSION_2_0 glDisable(GL_POLYGON_OFFSET_LINE); #endif } constantDepthBias_ = constantBias; slopeScaledDepthBias_ = slopeScaledBias; } } void Graphics::SetDepthTest(CompareMode mode) { if (mode != depthTestMode_) { glDepthFunc(glCmpFunc[mode]); depthTestMode_ = mode; } } void Graphics::SetDepthWrite(bool enable) { if (enable != depthWrite_) { glDepthMask(enable ? GL_TRUE : GL_FALSE); depthWrite_ = enable; } } void Graphics::SetFillMode(FillMode mode) { if (mode != fillMode_) { #ifndef GL_ES_VERSION_2_0 glPolygonMode(GL_FRONT_AND_BACK, mode == FILL_SOLID ? GL_FILL : GL_LINE); #endif fillMode_ = mode; } } void Graphics::SetScissorTest(bool enable, const Rect& rect, bool borderInclusive) { // During some light rendering loops, a full rect is toggled on/off repeatedly. // Disable scissor in that case to reduce state changes if (rect.min_.x_ <= 0.0f && rect.min_.y_ <= 0.0f && rect.max_.x_ >= 1.0f && rect.max_.y_ >= 1.0f) enable = false; if (enable) { IntVector2 rtSize(GetRenderTargetDimensions()); IntVector2 viewSize(viewport_.right_ - viewport_.left_, viewport_.bottom_ - viewport_.top_); IntVector2 viewPos(viewport_.left_, viewport_.top_); IntRect intRect; int expand = borderInclusive ? 1 : 0; intRect.left_ = Clamp((int)((rect.min_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_, 0, rtSize.x_ - 1); intRect.top_ = Clamp((int)((-rect.max_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_, 0, rtSize.y_ - 1); intRect.right_ = Clamp((int)((rect.max_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_ + expand, 0, rtSize.x_); intRect.bottom_ = Clamp((int)((-rect.min_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_ + expand, 0, rtSize.y_); if (intRect.right_ == intRect.left_) intRect.right_++; if (intRect.bottom_ == intRect.top_) intRect.bottom_++; if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_) enable = false; if (enable && scissorRect_ != intRect) { // Use Direct3D convention with the vertical coordinates ie. 0 is top glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.right_ - intRect.left_, intRect.bottom_ - intRect.top_); scissorRect_ = intRect; } } else scissorRect_ = IntRect::ZERO; if (enable != scissorTest_) { if (enable) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); scissorTest_ = enable; } } void Graphics::SetScissorTest(bool enable, const IntRect& rect) { IntVector2 rtSize(GetRenderTargetDimensions()); IntVector2 viewSize(viewport_.right_ - viewport_.left_, viewport_.bottom_ - viewport_.top_); IntVector2 viewPos(viewport_.left_, viewport_.top_); if (enable) { IntRect intRect; intRect.left_ = Clamp(rect.left_ + viewPos.x_, 0, rtSize.x_ - 1); intRect.top_ = Clamp(rect.top_ + viewPos.y_, 0, rtSize.y_ - 1); intRect.right_ = Clamp(rect.right_ + viewPos.x_, 0, rtSize.x_); intRect.bottom_ = Clamp(rect.bottom_ + viewPos.y_, 0, rtSize.y_); if (intRect.right_ == intRect.left_) intRect.right_++; if (intRect.bottom_ == intRect.top_) intRect.bottom_++; if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_) enable = false; if (enable && scissorRect_ != intRect) { // Use Direct3D convention with the vertical coordinates ie. 0 is top glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.right_ - intRect.left_, intRect.bottom_ - intRect.top_); scissorRect_ = intRect; } } else scissorRect_ = IntRect::ZERO; if (enable != scissorTest_) { if (enable) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); scissorTest_ = enable; } } void Graphics::SetStreamFrequency(unsigned index, unsigned frequency) { } void Graphics::ResetStreamFrequencies() { } void Graphics::SetStencilTest(bool enable, CompareMode mode, StencilOp pass, StencilOp fail, StencilOp zFail, unsigned stencilRef, unsigned compareMask, unsigned writeMask) { if (enable != stencilTest_) { if (enable) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); stencilTest_ = enable; } if (enable) { if (mode != stencilTestMode_ || stencilRef != stencilRef_ || compareMask != stencilCompareMask_) { glStencilFunc(glCmpFunc[mode], stencilRef, compareMask); stencilTestMode_ = mode; stencilRef_ = stencilRef; stencilCompareMask_ = compareMask; } if (writeMask != stencilWriteMask_) { glStencilMask(writeMask); stencilWriteMask_ = writeMask; } if (pass != stencilPass_ || fail != stencilFail_ || zFail != stencilZFail_) { glStencilOp(glStencilOps[fail], glStencilOps[zFail], glStencilOps[pass]); stencilPass_ = pass; stencilFail_ = fail; stencilZFail_ = zFail; } } } void Graphics::SetForceSM2(bool enable) { } bool Graphics::IsInitialized() const { return impl_->window_ != 0; } void* Graphics::GetWindowHandle() const { return impl_->window_; } PODVector Graphics::GetResolutions() const { PODVector ret; unsigned numModes = SDL_GetNumDisplayModes(0); for (unsigned i = 0; i < numModes; ++i) { SDL_DisplayMode mode; SDL_GetDisplayMode(0, i, &mode); int width = mode.w; int height = mode.h; // Store mode if unique bool unique = true; for (unsigned j = 0; j < ret.Size(); ++i) { if (ret[j].x_ == width && ret[j].y_ == height) { unique = false; break; } } if (unique) ret.Push(IntVector2(width, height)); } return ret; } PODVector Graphics::GetMultiSampleLevels() const { PODVector ret; // No multisampling always supported ret.Push(1); /// \todo Implement properly, if possible return ret; } VertexBuffer* Graphics::GetVertexBuffer(unsigned index) const { return index < MAX_VERTEX_STREAMS ? vertexBuffers_[index] : 0; } TextureUnit Graphics::GetTextureUnit(const String& name) { Map::Iterator i = textureUnits_.Find(name); if (i != textureUnits_.End()) return i->second_; else return MAX_TEXTURE_UNITS; } const String& Graphics::GetTextureUnitName(TextureUnit unit) { for (Map::Iterator i = textureUnits_.Begin(); i != textureUnits_.End(); ++i) { if (i->second_ == unit) return i->first_; } return noParameter; } Texture* Graphics::GetTexture(unsigned index) const { return index < MAX_TEXTURE_UNITS ? textures_[index] : 0; } RenderSurface* Graphics::GetRenderTarget(unsigned index) const { return index < MAX_RENDERTARGETS ? renderTargets_[index] : 0; } IntVector2 Graphics::GetRenderTargetDimensions() const { int width, height; if (renderTargets_[0]) { width = renderTargets_[0]->GetWidth(); height = renderTargets_[0]->GetHeight(); } else if (depthStencil_) { width = depthStencil_->GetWidth(); height = depthStencil_->GetHeight(); } else { width = width_; height = height_; } return IntVector2(width, height); } void Graphics::AddGPUObject(GPUObject* object) { gpuObjects_.Push(object); } void Graphics::RemoveGPUObject(GPUObject* object) { gpuObjects_.Erase(gpuObjects_.Find(object)); } void* Graphics::ReserveDiscardLockBuffer(unsigned size) { // First check for a free buffer that is large enough for (Vector::Iterator i = discardLockBuffers_.Begin(); i != discardLockBuffers_.End(); ++i) { if (!i->reserved_ && i->size_ >= size) { i->reserved_ = true; return i->data_.Get(); } } // Then check if a free buffer can be resized for (Vector::Iterator i = discardLockBuffers_.Begin(); i != discardLockBuffers_.End(); ++i) { if (!i->reserved_) { i->data_ = new unsigned char[size]; i->size_ = size; i->reserved_ = true; return i->data_.Get(); } } // Finally allocate a new buffer DiscardLockBuffer newBuffer; newBuffer.data_ = new unsigned char[size]; newBuffer.size_ = size; newBuffer.reserved_ = true; discardLockBuffers_.Push(newBuffer); return newBuffer.data_.Get(); } void Graphics::FreeDiscardLockBuffer(void* buffer) { for (Vector::Iterator i = discardLockBuffers_.Begin(); i != discardLockBuffers_.End(); ++i) { if (i->reserved_ && i->data_.Get() == buffer) { i->reserved_ = false; return; } } LOGWARNING("Reserved discard lock buffer " + ToStringHex((unsigned)buffer) + " not found"); } void Graphics::Release(bool clearGPUObjects, bool closeWindow) { if (!impl_->window_) return; CleanupFramebuffers(true); depthTextures_.Clear(); if (clearGPUObjects) { // Shutting down: release all GPU objects that still exist for (Vector::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i) (*i)->Release(); gpuObjects_.Clear(); } else { // We are not shutting down, but recreating the context: mark GPU objects lost for (Vector::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i) (*i)->OnDeviceLost(); } // When the new context is initialized, it will have default state again ResetCachedState(); ClearParameterSources(); { MutexLock lock(GetStaticMutex()); if (impl_->context_) { SDL_GL_DeleteContext(impl_->context_); impl_->context_ = 0; } if (closeWindow) { SDL_ShowCursor(SDL_TRUE); SDL_DestroyWindow(impl_->window_); impl_->window_ = 0; } } } void Graphics::CleanupRenderSurface(RenderSurface* surface) { if (!surface) return; // Flush pending FBO changes first if any CommitFramebuffer(); unsigned currentFbo = impl_->boundFbo_; // Go through all FBOs and clean up the surface from them for (Map::Iterator i = impl_->frameBuffers_.Begin(); i != impl_->frameBuffers_.End(); ++i) { for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j) { if (i->second_.colorAttachments_[j] == surface) { if (currentFbo != i->second_.fbo_) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, i->second_.fbo_); currentFbo = i->second_.fbo_; } glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + j, GL_TEXTURE_2D, 0, 0); i->second_.colorAttachments_[j] = 0; // Mark drawbuffer bits to need recalculation i->second_.drawBuffers_ = M_MAX_UNSIGNED; } } if (i->second_.depthAttachment_ == surface) { if (currentFbo != i->second_.fbo_) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, i->second_.fbo_); currentFbo = i->second_.fbo_; } glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); i->second_.depthAttachment_ = 0; } } // Restore previously bound FBO now if needed if (currentFbo != impl_->boundFbo_) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, impl_->boundFbo_); } unsigned Graphics::GetAlphaFormat() { return GL_ALPHA; } unsigned Graphics::GetLuminanceFormat() { return GL_LUMINANCE; } unsigned Graphics::GetLuminanceAlphaFormat() { return GL_LUMINANCE_ALPHA; } unsigned Graphics::GetRGBFormat() { return GL_RGB; } unsigned Graphics::GetRGBAFormat() { return GL_RGBA; } unsigned Graphics::GetFloatFormat() { #ifndef GL_ES_VERSION_2_0 return GL_LUMINANCE32F_ARB; #else return GL_LUMINANCE; #endif } unsigned Graphics::GetLinearDepthFormat() { // OpenGL FBO specs state that color attachments must have the same format; therefore must encode linear depth to RGBA // manually if not using a readable hardware depth texture return GL_RGBA; } unsigned Graphics::GetDepthStencilFormat() { #ifndef GL_ES_VERSION_2_0 return GL_DEPTH24_STENCIL8_EXT; #else return 0; #endif } void Graphics::CheckFeatureSupport() { // Check supported features: light pre-pass, deferred rendering and hardware depth texture lightPrepassSupport_ = false; deferredSupport_ = false; hardwareDepthSupport_ = false; int numSupportedRTs = 1; #ifndef GL_ES_VERSION_2_0 glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &numSupportedRTs); #else // For now disable shadow mapping on OpenGL ES shadowMapFormat_ = 0; hiresShadowMapFormat_ = 0; #endif // For now hardware depth texture is only tested for on NVIDIA hardware because of visual artifacts and slowdown on ATI String vendorString = String((const char*)glGetString(GL_VENDOR)).ToUpper(); if (vendorString.Find("NVIDIA") != String::NPOS) { SharedPtr depthTexture(new Texture2D(context_)); hardwareDepthSupport_ = true; // Note: Texture2D::SetSize() requires hardwareDepthSupport_ == true to create a texture instead of a renderbuffer depthTexture->SetSize(256, 256, GetDepthStencilFormat(), TEXTURE_DEPTHSTENCIL); SetDepthStencil(depthTexture); // If hardware depth textures work, this means also light pre-pass is automatically supported if (CheckFramebuffer()) { lightPrepassSupport_ = true; if (numSupportedRTs >= 3) deferredSupport_ = true; } else hardwareDepthSupport_ = false; ResetDepthStencil(); } if (!hardwareDepthSupport_) { // If hardware depth is not supported, must support 2 rendertargets for light pre-pass, and 4 for deferred if (numSupportedRTs >= 2) lightPrepassSupport_ = true; if (numSupportedRTs >= 4) deferredSupport_ = true; } } void Graphics::CommitFramebuffer() { if (!impl_->fboDirty_) return; impl_->fboDirty_ = false; // First check if no framebuffer is needed. In that case simply return to backbuffer rendering bool noFbo = !depthStencil_; if (noFbo) { for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i) { if (renderTargets_[i]) { noFbo = false; break; } } } if (noFbo) { if (impl_->boundFbo_) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); impl_->boundFbo_ = 0; } return; } // Search for a new framebuffer based on format & size, or create new IntVector2 rtSize = Graphics::GetRenderTargetDimensions(); unsigned format = 0; if (renderTargets_[0]) format = renderTargets_[0]->GetParentTexture()->GetFormat(); else if (depthStencil_) format = depthStencil_->GetParentTexture()->GetFormat(); unsigned long long fboKey = (rtSize.x_ << 16 | rtSize.y_) | (((unsigned long long)format) << 32); Map::Iterator i = impl_->frameBuffers_.Find(fboKey); if (i == impl_->frameBuffers_.End()) { FrameBufferObject newFbo; glGenFramebuffersEXT(1, &newFbo.fbo_); i = impl_->frameBuffers_.Insert(MakePair(fboKey, newFbo)); } i->second_.useTimer_.Reset(); if (impl_->boundFbo_ != i->second_.fbo_) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, i->second_.fbo_); impl_->boundFbo_ = i->second_.fbo_; } #ifndef GL_ES_VERSION_2_0 // Setup readbuffers & drawbuffers if needed if (i->second_.readBuffers_ != GL_NONE) { glReadBuffer(GL_NONE); i->second_.readBuffers_ = GL_NONE; } // Calculate the bit combination of non-zero color rendertargets to first check if the combination changed unsigned newDrawBuffers = 0; for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i) { if (renderTargets_[i]) newDrawBuffers |= 1 << i; } if (newDrawBuffers != i->second_.drawBuffers_) { // Check for no color rendertargets (depth rendering only) if (!newDrawBuffers) glDrawBuffer(GL_NONE); else { int drawBufferIds[4]; unsigned drawBufferCount = 0; for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i) { if (renderTargets_[i]) drawBufferIds[drawBufferCount++] = GL_COLOR_ATTACHMENT0_EXT + i; } glDrawBuffers(drawBufferCount, (const GLenum*)drawBufferIds); } i->second_.drawBuffers_ = newDrawBuffers; } #endif for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j) { if (renderTargets_[j]) { Texture* texture = renderTargets_[j]->GetParentTexture(); // If texture's parameters are dirty, update before attaching if (texture->GetParametersDirty()) { SetTextureForUpdate(texture); texture->UpdateParameters(); SetTexture(0, 0); } if (i->second_.colorAttachments_[j] != renderTargets_[j]) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + j, renderTargets_[j]->GetTarget(), texture->GetGPUObject(), 0); i->second_.colorAttachments_[j] = renderTargets_[j]; } } else { if (i->second_.colorAttachments_[j]) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + j, GL_TEXTURE_2D, 0, 0); i->second_.colorAttachments_[j] = 0; } } } if (depthStencil_) { // Bind either a renderbuffer or a depth texture, depending on what is available Texture* texture = depthStencil_->GetParentTexture(); bool hasStencil = texture->GetFormat() == GetDepthStencilFormat(); unsigned renderBufferID = depthStencil_->GetRenderBuffer(); if (!renderBufferID) { // If texture's parameters are dirty, update before attaching if (texture->GetParametersDirty()) { SetTextureForUpdate(texture); texture->UpdateParameters(); SetTexture(0, 0); } if (i->second_.depthAttachment_ != depthStencil_) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texture->GetGPUObject(), 0); if (hasStencil) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, texture->GetGPUObject(), 0); } else glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); i->second_.depthAttachment_ = depthStencil_; } } else { if (i->second_.depthAttachment_ != depthStencil_) { glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderBufferID); if (hasStencil) { glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderBufferID); } else glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); i->second_.depthAttachment_ = depthStencil_; } } impl_->depthBits_ = texture->GetDepthBits(); } else { if (i->second_.depthAttachment_) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); i->second_.depthAttachment_ = 0; impl_->depthBits_ = impl_->windowDepthBits_; } } } bool Graphics::CheckFramebuffer() { return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT; } void Graphics::CleanupFramebuffers(bool deleteAll) { if (deleteAll && impl_->boundFbo_) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); impl_->boundFbo_ = 0; } for (Map::Iterator i = impl_->frameBuffers_.Begin(); i != impl_->frameBuffers_.End();) { Map::Iterator current = i++; if (deleteAll || (current->second_.fbo_ != impl_->boundFbo_ && current->second_.useTimer_.GetMSec(false) > MAX_FRAMEBUFFER_AGE)) { glDeleteFramebuffersEXT(1, ¤t->second_.fbo_); impl_->frameBuffers_.Erase(current); } } } void Graphics::ResetCachedState() { for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) { vertexBuffers_[i] = 0; elementMasks_[i] = 0; } for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i) { textures_[i] = 0; textureTypes_[i] = 0; } for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i) renderTargets_[i] = 0; depthStencil_ = 0; viewTexture_ = 0; viewport_ = IntRect(0, 0, 0, 0); indexBuffer_ = 0; vertexShader_ = 0; pixelShader_ = 0; shaderProgram_ = 0; blendMode_ = BLEND_REPLACE; textureAnisotropy_ = 1; colorWrite_ = true; cullMode_ = CULL_NONE; constantDepthBias_ = 0.0f; slopeScaledDepthBias_ = 0.0f; depthTestMode_ = CMP_ALWAYS; depthWrite_ = true; fillMode_ = FILL_SOLID; scissorTest_ = false; scissorRect_ = IntRect::ZERO; stencilTest_ = false; stencilTestMode_ = CMP_ALWAYS; stencilPass_ = OP_KEEP; stencilFail_ = OP_KEEP; stencilZFail_ = OP_KEEP; stencilRef_ = 0; stencilCompareMask_ = M_MAX_UNSIGNED; stencilWriteMask_ = M_MAX_UNSIGNED; impl_->activeTexture_ = 0; impl_->enabledAttributes_ = 0; } void Graphics::SetTextureUnitMappings() { textureUnits_["DiffMap"] = TU_DIFFUSE; textureUnits_["DiffCubeMap"] = TU_DIFFUSE; textureUnits_["NormalMap"] = TU_NORMAL; textureUnits_["EmissiveMap"] = TU_EMISSIVE; textureUnits_["DetailMap"] = TU_DETAIL; textureUnits_["EnvironmentMap"] = TU_ENVIRONMENT; textureUnits_["EnvironmentCubeMap"] = TU_ENVIRONMENT; textureUnits_["LightRampMap"] = TU_LIGHTRAMP; textureUnits_["LightSpotMap"] = TU_LIGHTSHAPE; textureUnits_["LightCubeMap"] = TU_LIGHTSHAPE; textureUnits_["ShadowMap"] = TU_SHADOWMAP; textureUnits_["FaceSelectCubeMap"] = TU_FACESELECT; textureUnits_["IndirectionCubeMap"] = TU_INDIRECTION; textureUnits_["AlbedoBuffer"] = TU_ALBEDOBUFFER; textureUnits_["NormalBuffer"] = TU_NORMALBUFFER; textureUnits_["DepthBuffer"] = TU_DEPTHBUFFER; textureUnits_["LightBuffer"] = TU_LIGHTBUFFER; } void RegisterGraphicsLibrary(Context* context) { Animation::RegisterObject(context); Material::RegisterObject(context); Model::RegisterObject(context); Shader::RegisterObject(context); Technique::RegisterObject(context); Texture2D::RegisterObject(context); TextureCube::RegisterObject(context); Camera::RegisterObject(context); Drawable::RegisterObject(context); Light::RegisterObject(context); StaticModel::RegisterObject(context); Skybox::RegisterObject(context); AnimatedModel::RegisterObject(context); AnimationController::RegisterObject(context); BillboardSet::RegisterObject(context); ParticleEmitter::RegisterObject(context); DebugRenderer::RegisterObject(context); Octree::RegisterObject(context); Zone::RegisterObject(context); }