// Copyright (C) 2015 Jérôme Leclercq // This file is part of the "Nazara Engine - Renderer module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Nz { namespace { struct Attachment { NazaraSlot(RenderBuffer, OnRenderBufferDestroy, renderBufferDestroySlot); NazaraSlot(Texture, OnTextureDestroy, textureDestroySlot); RenderBufferRef buffer; TextureRef texture; AttachmentPoint attachmentPoint; bool isBuffer; bool isUsed = false; unsigned int height; unsigned int width; }; unsigned int attachmentIndex[AttachmentPoint_Max+1] = { 3, // AttachmentPoint_Color 0, // AttachmentPoint_Depth 1, // AttachmentPoint_DepthStencil 2 // AttachmentPoint_Stencil }; AttachmentPoint formatTypeToAttachment[PixelFormatTypeType_Max+1] = { AttachmentPoint_Color, // PixelFormatTypeType_Color AttachmentPoint_Depth, // PixelFormatTypeType_Depth AttachmentPoint_DepthStencil, // PixelFormatTypeType_DepthStencil AttachmentPoint_Stencil // PixelFormatTypeType_Stencil }; GLuint lockedPrevious = 0; UInt8 lockedLevel = 0; } struct RenderTextureImpl { NazaraSlot(Context, OnContextDestroy, contextDestroySlot); GLuint fbo; std::vector attachments; std::vector colorTargets; mutable std::vector drawBuffers; const Context* context; bool complete = false; bool userDefinedTargets = false; unsigned int height; unsigned int width; }; bool RenderTexture::AttachBuffer(AttachmentPoint attachmentPoint, UInt8 index, RenderBuffer* buffer) { #if NAZARA_RENDERER_SAFE if (!m_impl) { NazaraError("Render texture not created"); return false; } if (attachmentPoint != AttachmentPoint_Color) { if (index > 0) { NazaraError("Index must be 0 for non-color attachments"); return false; } } else { if (index >= Renderer::GetMaxColorAttachments()) { NazaraError("Color index is over max color attachments (" + String::Number(index) + " >= " + String::Number(Renderer::GetMaxColorAttachments()) + ")"); return false; } } if (!buffer || !buffer->IsValid()) { NazaraError("Invalid render buffer"); return false; } unsigned int depthStencilIndex = attachmentIndex[AttachmentPoint_DepthStencil]; if (m_impl->attachments.size() > depthStencilIndex && m_impl->attachments[depthStencilIndex].isUsed) { if (attachmentPoint == AttachmentPoint_Depth) { NazaraError("Depth target already attached by DepthStencil attachment"); return false; } else if (attachmentPoint == AttachmentPoint_Stencil) { NazaraError("Stencil target already attached by DepthStencil attachment"); return false; } } AttachmentPoint targetAttachmentPoint = formatTypeToAttachment[PixelFormat::GetType(buffer->GetFormat())]; if (targetAttachmentPoint != attachmentPoint && targetAttachmentPoint != AttachmentPoint_DepthStencil && attachmentPoint != AttachmentPoint_Depth && attachmentPoint != AttachmentPoint_Stencil) { NazaraError("Pixel format type does not match attachment point type"); return false; } #endif if (!Lock()) { NazaraError("Failed to lock render texture"); return false; } // Détachement de l'attache précédente (Si il y a) Detach(attachmentPoint, index); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, GL_RENDERBUFFER, buffer->GetOpenGLID()); Unlock(); unsigned int attachIndex = attachmentIndex[attachmentPoint] + index; if (attachIndex >= m_impl->attachments.size()) m_impl->attachments.resize(attachIndex+1); Attachment& attachment = m_impl->attachments[attachIndex]; attachment.attachmentPoint = attachmentPoint; attachment.buffer = buffer; attachment.renderBufferDestroySlot.Connect(buffer->OnRenderBufferDestroy, std::bind(&RenderTexture::OnRenderBufferDestroy, this, std::placeholders::_1, attachIndex)); attachment.isBuffer = true; attachment.isUsed = true; attachment.height = buffer->GetHeight(); attachment.width = buffer->GetWidth(); InvalidateSize(); InvalidateTargets(); return true; } bool RenderTexture::AttachBuffer(AttachmentPoint attachmentPoint, UInt8 index, PixelFormatType format, unsigned int width, unsigned int height) { RenderBufferRef renderBuffer = RenderBuffer::New(); if (!renderBuffer->Create(format, width, height)) { NazaraError("Failed to create RenderBuffer"); return false; } if (!AttachBuffer(attachmentPoint, index, renderBuffer)) { NazaraError("Failed to attach buffer"); return false; } return true; } bool RenderTexture::AttachTexture(AttachmentPoint attachmentPoint, UInt8 index, Texture* texture, unsigned int z) { #if NAZARA_RENDERER_SAFE if (!m_impl) { NazaraError("Render texture not created"); return false; } if (attachmentPoint != AttachmentPoint_Color) { if (index > 0) { NazaraError("Index must be 0 for non-color attachments"); return false; } } else { if (index >= Renderer::GetMaxColorAttachments()) { NazaraError("Color index is over max color attachments (" + String::Number(index) + " >= " + String::Number(Renderer::GetMaxColorAttachments()) + ")"); return false; } } if (attachmentPoint == AttachmentPoint_Stencil) { NazaraError("Targeting stencil-only textures is not supported"); return false; } unsigned int depthStencilIndex = attachmentIndex[AttachmentPoint_DepthStencil]; if (attachmentPoint == AttachmentPoint_Depth && m_impl->attachments.size() > depthStencilIndex && m_impl->attachments[depthStencilIndex].isUsed) { NazaraError("Depth target already attached by DepthStencil attachment"); return false; } if (!texture || !texture->IsValid()) { NazaraError("Invalid texture"); return false; } unsigned int depth = (texture->GetType() == ImageType_Cubemap) ? 6 : texture->GetDepth(); if (z >= depth) { NazaraError("Z value exceeds depth (" + String::Number(z) + " >= (" + String::Number(depth) + ')'); return false; } AttachmentPoint targetAttachmentPoint = formatTypeToAttachment[PixelFormat::GetType(texture->GetFormat())]; if (targetAttachmentPoint != attachmentPoint && targetAttachmentPoint != AttachmentPoint_DepthStencil && attachmentPoint != AttachmentPoint_Depth && attachmentPoint != AttachmentPoint_Stencil) { NazaraError("Pixel format type does not match attachment point type"); return false; } #endif if (!Lock()) { NazaraError("Failed to lock render texture"); return false; } // Détachement de l'attache précédente (Si il y a) Detach(attachmentPoint, index); switch (texture->GetType()) { case ImageType_1D: glFramebufferTexture1D(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, GL_TEXTURE_1D, texture->GetOpenGLID(), 0); break; case ImageType_1D_Array: case ImageType_2D_Array: glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, texture->GetOpenGLID(), 0, z); break; case ImageType_2D: glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, GL_TEXTURE_2D, texture->GetOpenGLID(), 0); break; case ImageType_3D: glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, GL_TEXTURE_3D, texture->GetOpenGLID(), 0, z); break; case ImageType_Cubemap: glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, OpenGL::CubemapFace[z], texture->GetOpenGLID(), 0); break; } Unlock(); unsigned int attachIndex = attachmentIndex[attachmentPoint] + index; if (attachIndex >= m_impl->attachments.size()) m_impl->attachments.resize(attachIndex+1); Attachment& attachment = m_impl->attachments[attachIndex]; attachment.attachmentPoint = attachmentPoint; attachment.isBuffer = false; attachment.isUsed = true; attachment.height = texture->GetHeight(); attachment.texture = texture; attachment.textureDestroySlot.Connect(texture->OnTextureDestroy, std::bind(&RenderTexture::OnTextureDestroy, this, std::placeholders::_1, attachIndex)); attachment.width = texture->GetWidth(); InvalidateSize(); InvalidateTargets(); return true; } bool RenderTexture::Create(bool lock) { Destroy(); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() == nullptr) { NazaraError("No active context"); return false; } #endif std::unique_ptr impl(new RenderTextureImpl); impl->fbo = 0; glGenFramebuffers(1, &impl->fbo); if (!impl->fbo) { NazaraError("Failed to create framebuffer"); return false; } m_impl = impl.release(); m_impl->context = Context::GetCurrent(); m_impl->contextDestroySlot.Connect(m_impl->context->OnContextDestroy, this, &RenderTexture::OnContextDestroy); m_checked = false; m_drawBuffersUpdated = true; m_sizeUpdated = false; m_targetsUpdated = true; if (lock) { // En cas d'exception, la ressource sera quand même libérée CallOnExit onExit([this] () { Destroy(); }); if (!Lock()) { NazaraError("Failed to lock render texture"); return false; } onExit.Reset(); } OnRenderTargetParametersChange(this); OnRenderTargetSizeChange(this); return true; } void RenderTexture::Destroy() { if (m_impl) { if (IsActive()) Renderer::SetTarget(nullptr); // Le FBO devant être supprimé dans son contexte d'origine, nous déléguons sa suppression à la classe OpenGL // Celle-ci va libérer le FBO dès que possible (la prochaine fois que son contexte d'origine sera actif) OpenGL::DeleteFrameBuffer(m_impl->context, m_impl->fbo); delete m_impl; // Enlève également une références sur les Texture/RenderBuffer m_impl = nullptr; } } void RenderTexture::Detach(AttachmentPoint attachmentPoint, UInt8 index) { #if NAZARA_RENDERER_SAFE if (!m_impl) { NazaraError("Render texture not created"); return; } if (attachmentPoint != AttachmentPoint_Color && index > 0) { NazaraError("Index must be 0 for non-color attachments"); return; } #endif unsigned int attachIndex = attachmentIndex[attachmentPoint] + index; if (attachIndex >= m_impl->attachments.size()) return; Attachment& attachement = m_impl->attachments[attachIndex]; if (!attachement.isUsed) return; if (!Lock()) { NazaraError("Failed to lock render texture"); return; } attachement.isUsed = false; if (attachement.isBuffer) { glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, GL_RENDERBUFFER, 0); attachement.buffer = nullptr; attachement.renderBufferDestroySlot.Disconnect(); } else { if (glFramebufferTexture) glFramebufferTexture(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, 0, 0); else glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, OpenGL::Attachment[attachmentPoint]+index, 0, 0, 0); attachement.texture = nullptr; attachement.textureDestroySlot.Disconnect(); } InvalidateSize(); if (attachement.attachmentPoint == AttachmentPoint_Color) InvalidateTargets(); Unlock(); m_checked = false; } unsigned int RenderTexture::GetHeight() const { NazaraAssert(m_impl, "Invalid render texture"); if (!m_sizeUpdated) UpdateSize(); return m_impl->height; } RenderTargetParameters RenderTexture::GetParameters() const { NazaraAssert(m_impl, "Invalid render texture"); ///TODO return RenderTargetParameters(); } Vector2ui RenderTexture::GetSize() const { NazaraAssert(m_impl, "Invalid render texture"); if (!m_sizeUpdated) UpdateSize(); return Vector2ui(m_impl->width, m_impl->height); } unsigned int RenderTexture::GetWidth() const { NazaraAssert(m_impl, "Invalid render texture"); if (!m_sizeUpdated) UpdateSize(); return m_impl->width; } bool RenderTexture::IsComplete() const { NazaraAssert(m_impl, "Invalid render texture"); if (!m_checked) { if (!Lock()) { NazaraError("Failed to lock render texture"); return false; } GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); Unlock(); m_impl->complete = false; switch (status) { case GL_FRAMEBUFFER_COMPLETE: m_impl->complete = true; break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: NazaraError("Incomplete attachment"); break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: NazaraInternalError("Incomplete draw buffer"); break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: NazaraInternalError("Incomplete read buffer"); break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: NazaraError("Incomplete missing attachment"); break; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: NazaraError("Incomplete multisample"); break; case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: NazaraError("Incomplete layer targets"); break; case GL_FRAMEBUFFER_UNSUPPORTED: NazaraError("Render texture has unsupported attachments"); break; default: NazaraInternalError("Unknown error"); } m_checked = true; } return m_impl->complete; } bool RenderTexture::IsRenderable() const { return IsComplete() && !m_impl->attachments.empty(); } bool RenderTexture::Lock() const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() != m_impl->context) { NazaraError("RenderTexture cannot be used with this context"); return false; } #endif if (lockedLevel++ == 0) { GLint previous; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous); lockedPrevious = previous; if (lockedPrevious != m_impl->fbo) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_impl->fbo); } return true; } void RenderTexture::SetColorTargets(const UInt8* targets, unsigned int targetCount) const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE for (unsigned int i = 0; i < targetCount; ++i) { unsigned int index = attachmentIndex[AttachmentPoint_Color] + targets[i]; if (index >= m_impl->attachments.size() || !m_impl->attachments[index].isUsed) { NazaraError("Target " + String::Number(targets[i]) + " not attached"); return; } } #endif m_impl->colorTargets.resize(targetCount); std::memcpy(&m_impl->colorTargets[0], targets, targetCount*sizeof(UInt8)); m_impl->userDefinedTargets = true; InvalidateDrawBuffers(); } void RenderTexture::SetColorTargets(const std::initializer_list& targets) const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE for (UInt8 target : targets) { unsigned int index = attachmentIndex[AttachmentPoint_Color] + target; if (index >= m_impl->attachments.size() || !m_impl->attachments[index].isUsed) { NazaraError("Target " + String::Number(target) + " not attached"); return; } } #endif m_impl->colorTargets.resize(targets.size()); UInt8* ptr = &m_impl->colorTargets[0]; for (UInt8 index : targets) *ptr++ = index; m_impl->userDefinedTargets = true; InvalidateDrawBuffers(); } void RenderTexture::Unlock() const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() != m_impl->context) { NazaraError("RenderTexture cannot be used with this context"); return; } if (lockedLevel == 0) { NazaraWarning("Unlock called on non-locked texture"); return; } #endif if (--lockedLevel == 0 && lockedPrevious != m_impl->fbo) // Ici, il est important qu'un FBO soit débindé si l'ancien était 0 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, lockedPrevious); } unsigned int RenderTexture::GetOpenGLID() const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() != m_impl->context) { NazaraError("RenderTexture cannot be used with this context"); return 0; } #endif return m_impl->fbo; } bool RenderTexture::HasContext() const { return false; } void RenderTexture::Blit(RenderTexture* src, Rectui srcRect, RenderTexture* dst, Rectui dstRect, UInt32 buffers, bool bilinearFilter) { NazaraAssert(src && src->IsValid(), "Invalid source render texture"); NazaraAssert(dst && dst->IsValid(), "Invalid destination render texture"); #if NAZARA_RENDERER_SAFE if (srcRect.x+srcRect.width > src->GetWidth() || srcRect.y+srcRect.height > src->GetHeight()) { NazaraError("Source rectangle dimensions are out of bounds"); return; } if (dstRect.x+dstRect.width > dst->GetWidth() || dstRect.y+dstRect.height > dst->GetHeight()) { NazaraError("Destination rectangle dimensions are out of bounds"); return; } if (bilinearFilter && (buffers & RendererBuffer_Depth || buffers & RendererBuffer_Stencil)) { NazaraError("Filter cannot be bilinear when blitting depth/stencil buffers"); return; } #endif GLbitfield mask = 0; if (buffers & RendererBuffer_Color) mask |= GL_COLOR_BUFFER_BIT; if (buffers & RendererBuffer_Depth) mask |= GL_DEPTH_BUFFER_BIT; if (buffers & RendererBuffer_Stencil) mask |= GL_STENCIL_BUFFER_BIT; GLint previousDrawBuffer, previousReadBuffer; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previousDrawBuffer); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &previousReadBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->GetOpenGLID()); glBindFramebuffer(GL_READ_FRAMEBUFFER, src->GetOpenGLID()); glBlitFramebuffer(srcRect.x, srcRect.y, srcRect.x + srcRect.width, srcRect.y + srcRect.height, dstRect.x, dstRect.y, dstRect.x + dstRect.width, dstRect.y + dstRect.height, mask, (bilinearFilter) ? GL_LINEAR : GL_NEAREST); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previousDrawBuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, previousReadBuffer); } bool RenderTexture::Activate() const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() != m_impl->context) { NazaraError("RenderTexture cannot be used with this context"); return false; } #endif glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_impl->fbo); m_drawBuffersUpdated = false; return true; } void RenderTexture::Desactivate() const { NazaraAssert(m_impl, "Invalid render texture"); #if NAZARA_RENDERER_SAFE if (Context::GetCurrent() != m_impl->context) { NazaraError("RenderTexture cannot be used with this context"); return; } #endif glFlush(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } void RenderTexture::EnsureTargetUpdated() const { if (!m_drawBuffersUpdated) UpdateDrawBuffers(); for (UInt8 index : m_impl->colorTargets) { Attachment& attachment = m_impl->attachments[attachmentIndex[AttachmentPoint_Color] + index]; if (!attachment.isBuffer) attachment.texture->InvalidateMipmaps(); } } void RenderTexture::OnContextDestroy(const Context* context) { NazaraAssert(m_impl, "Invalid internal state"); NazaraUnused(context); #ifdef NAZARA_DEBUG if (m_impl->context != context) { NazaraInternalError("Not listening to " + String::Pointer(context)); return; } #endif Destroy(); } void RenderTexture::OnRenderBufferDestroy(const RenderBuffer* renderBuffer, unsigned int attachmentIndex) { NazaraAssert(m_impl, "Invalid internal state"); NazaraAssert(attachmentIndex < m_impl->attachments.size(), "Invalid attachment index"); NazaraAssert(m_impl->attachments[attachmentIndex].isBuffer, "Invalid attachment state"); NazaraUnused(renderBuffer); Attachment& attachment = m_impl->attachments[attachmentIndex]; attachment.buffer = nullptr; attachment.isUsed = false; attachment.renderBufferDestroySlot.Disconnect(); InvalidateTargets(); } void RenderTexture::OnTextureDestroy(const Texture* texture, unsigned int attachmentIndex) { NazaraAssert(m_impl, "Invalid internal state"); NazaraAssert(attachmentIndex < m_impl->attachments.size(), "Invalid attachment index"); NazaraAssert(!m_impl->attachments[attachmentIndex].isBuffer, "Invalid attachment state"); NazaraUnused(texture); InvalidateTargets(); } void RenderTexture::UpdateDrawBuffers() const { if (!m_targetsUpdated) UpdateTargets(); glDrawBuffers(m_impl->drawBuffers.size(), &m_impl->drawBuffers[0]); m_drawBuffersUpdated = true; } void RenderTexture::UpdateSize() const { m_impl->width = 0; m_impl->height = 0; for (Attachment& attachment : m_impl->attachments) { if (attachment.isUsed) { m_impl->height = std::max(m_impl->height, attachment.height); m_impl->width = std::max(m_impl->width, attachment.width); } } m_sizeUpdated = true; } void RenderTexture::UpdateTargets() const { if (!m_impl->userDefinedTargets) { m_impl->colorTargets.clear(); unsigned int colorIndex = 0; for (unsigned int index = attachmentIndex[AttachmentPoint_Color]; index < m_impl->attachments.size(); ++index) m_impl->colorTargets.push_back(colorIndex++); } if (m_impl->colorTargets.empty()) { m_impl->drawBuffers.resize(1); m_impl->drawBuffers[0] = GL_NONE; } else { m_impl->drawBuffers.resize(m_impl->colorTargets.size()); GLenum* ptr = &m_impl->drawBuffers[0]; for (UInt8 index : m_impl->colorTargets) *ptr++ = GL_COLOR_ATTACHMENT0 + index; } m_targetsUpdated = true; } }