/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2014-2015, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include "C4Include.h" #include "C4FoWRegion.h" #include "C4DrawGL.h" C4FoWRegion::C4FoWRegion(C4FoW *pFoW, C4Player *pPlayer) : pFoW(pFoW) , pPlayer(pPlayer) #ifndef USE_CONSOLE , hFrameBufDraw(0), hFrameBufRead(0), hVBO(0) #endif , Region(0,0,0,0), OldRegion(0,0,0,0) , pSurface(new C4Surface), pBackSurface(new C4Surface) { ViewportRegion.left = ViewportRegion.right = ViewportRegion.top = ViewportRegion.bottom = 0.0f; } C4FoWRegion::~C4FoWRegion() { #ifndef USE_CONSOLE if (hFrameBufDraw) { glDeleteFramebuffersEXT(1, &hFrameBufDraw); glDeleteFramebuffersEXT(1, &hFrameBufRead); } if (hVBO) { glDeleteBuffers(1, &hVBO); } #endif } bool C4FoWRegion::BindFramebuf() { #ifndef USE_CONSOLE // Flip texture pSurface.swap(pBackSurface); // Can simply reuse old texture? if (pSurface->Wdt < Region.Wdt || (pSurface->Hgt / 2) < Region.Hgt) { // Determine texture size. Round up to next power of two in order to // prevent rounding errors, as well as preventing lots of // re-allocations when region size changes quickly (think zoom). int iWdt = 1, iHgt = 1; while (iWdt < Region.Wdt) iWdt *= 2; while (iHgt < Region.Hgt) iHgt *= 2; // Double the texture size. The second half of the texture // will contain the light color information, while the // first half contains the brightness and direction information iHgt *= 2; // Create the new surfaces std::unique_ptr pNewSurface(new C4Surface); std::unique_ptr pNewBackSurface(new C4Surface); if (!pNewSurface->Create(iWdt, iHgt, false, 0, 0)) return false; if (!pNewBackSurface->Create(iWdt, iHgt, false, 0, 0)) return false; // Copy over old content. This avoids flicker in already // explored regions that might get temporarily dark and // re-faded-in with the surface swap. New area in the surface // is initialized with darkness (black). pSurface->Lock(); pBackSurface->Lock(); pNewSurface->Lock(); pNewBackSurface->Lock(); // Take into account that the texture // is split for normals/intensity and colors, and also that // OpenGL textures are upside down. for (int y = 0; y < iHgt / 2; ++y) { for (int x = 0; x < iWdt; ++x) { if (y < pSurface->Hgt / 2 && x < pSurface->Wdt) { // Normals and intensity pNewSurface->SetPixDw(x, pNewSurface->Hgt/2 - y - 1, pSurface->GetPixDw(x, pSurface->Hgt/2 - y - 1, false)); pNewBackSurface->SetPixDw(x, pNewBackSurface->Hgt/2 - y - 1, pBackSurface->GetPixDw(x, pBackSurface->Hgt/2 - y - 1, false)); // Color pNewSurface->SetPixDw(x, pNewSurface->Hgt/2 - y + iHgt / 2 - 1, pSurface->GetPixDw(x, pSurface->Hgt/2 - y + pSurface->Hgt / 2 - 1, false)); pNewBackSurface->SetPixDw(x, pNewBackSurface->Hgt/2 - y + iHgt / 2 - 1, pBackSurface->GetPixDw(x, pBackSurface->Hgt/2 - y + pBackSurface->Hgt / 2 - 1, false)); } else { // Normals and intensity pNewSurface->SetPixDw(x, pNewSurface->Hgt/2 - y - 1, 0x000000ff); pNewBackSurface->SetPixDw(x, pNewBackSurface->Hgt/2 - y - 1, 0x000000ff); // Color pNewSurface->SetPixDw(x, pNewSurface->Hgt/2 - y + iHgt / 2 - 1, 0x000000ff); pNewBackSurface->SetPixDw(x, pNewBackSurface->Hgt/2 - y + iHgt / 2 - 1, 0x000000ff); } } } pSurface = std::move(pNewSurface); pBackSurface = std::move(pNewBackSurface); pSurface->Unlock(); pBackSurface->Unlock(); } // Cannot bind empty surface if (!pSurface->iTexSize) return false; // Generate frame buffer object if (!hFrameBufDraw) { glGenFramebuffersEXT(1, &hFrameBufDraw); glGenFramebuffersEXT(1, &hFrameBufRead); } // Bind current texture to frame buffer glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, hFrameBufDraw); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, hFrameBufRead); glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, pSurface->textures[0].texName, 0); if (!pBackSurface->textures.empty()) glFramebufferTexture2DEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, pBackSurface->textures[0].texName, 0); // Check status, unbind if something was amiss GLenum status1 = glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT), status2 = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT); if (status1 != GL_FRAMEBUFFER_COMPLETE_EXT || (pBackSurface && status2 != GL_FRAMEBUFFER_COMPLETE_EXT)) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); return false; } #endif // Worked! return true; } int32_t C4FoWRegion::getSurfaceHeight() const { return pSurface->Hgt; } int32_t C4FoWRegion::getSurfaceWidth() const { return pSurface->Wdt; } #ifndef USE_CONSOLE GLuint C4FoWRegion::getSurfaceName() const { assert(!pSurface->textures.empty()); if (pSurface->textures.empty()) return 0; return pSurface->textures[0].texName; } #endif void C4FoWRegion::Update(C4Rect r, const FLOAT_RECT& vp) { // Set the new region Region = r; ViewportRegion = vp; } bool C4FoWRegion::Render(const C4TargetFacet *pOnScreen) { #ifndef USE_CONSOLE // Update FoW at interesting location pFoW->Update(Region, pPlayer); // On screen? No need to set up frame buffer - simply shortcut if (pOnScreen) { pFoW->Render(this, pOnScreen, pPlayer, pGL->GetProjectionMatrix()); return true; } // Set up shader. If this one doesn't work, we're really in trouble. C4Shader *pShader = pFoW->GetFramebufShader(); assert(pShader); if (!pShader) return false; // Create & bind the frame buffer pDraw->StorePrimaryClipper(); if(!BindFramebuf()) { pDraw->RestorePrimaryClipper(); return false; } assert(pSurface && hFrameBufDraw); if (!pSurface || !hFrameBufDraw) return false; // Set up a clean context glViewport(0, 0, pSurface->Wdt, pSurface->Hgt); const StdProjectionMatrix projectionMatrix = StdProjectionMatrix::Orthographic(0.0f, pSurface->Wdt, pSurface->Hgt, 0.0f); // Clear texture contents assert(pSurface->Hgt % 2 == 0); glScissor(0, pSurface->Hgt / 2, pSurface->Wdt, pSurface->Hgt / 2); glClearColor(0.0f, 0.5f / 1.5f, 0.5f / 1.5f, 0.0f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); // clear lower half of texture glScissor(0, 0, pSurface->Wdt, pSurface->Hgt / 2); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // Render FoW to frame buffer object glBlendFunc(GL_ONE, GL_ONE); pFoW->Render(this, NULL, pPlayer, projectionMatrix); // Copy over the old state if (OldRegion.Wdt > 0) { // How much the borders have moved int dx0 = Region.x - OldRegion.x, dy0 = Region.y - OldRegion.y, dx1 = Region.x + Region.Wdt - OldRegion.x - OldRegion.Wdt, dy1 = Region.y + Region.Hgt - OldRegion.y - OldRegion.Hgt; // Source and target rect coordinates (landscape coordinate system) int sx0 = std::max(0, dx0), sy0 = std::max(0, dy0), sx1 = OldRegion.Wdt - std::max(0, -dx1), sy1 = OldRegion.Hgt - std::max(0, -dy1), tx0 = std::max(0, -dx0), ty0 = std::max(0, -dy0), tx1 = Region.Wdt - std::max(0, dx1), ty1 = Region.Hgt - std::max(0, dy1); // Quad coordinates float vtxData[16]; float* squad = &vtxData[0]; float* tquad = &vtxData[8]; squad[0] = float(sx0); squad[1] = float(sy0); squad[2] = float(sx0); squad[3] = float(sy1); squad[4] = float(sx1); squad[5] = float(sy0); squad[6] = float(sx1); squad[7] = float(sy1); tquad[0] = float(tx0); tquad[1] = float(ty0); tquad[2] = float(tx0); tquad[3] = float(ty1); tquad[4] = float(tx1); tquad[5] = float(ty0); tquad[6] = float(tx1); tquad[7] = float(ty1); // Transform into texture coordinates for (int i = 0; i < 4; i++) { squad[i*2] = squad[i*2] / pBackSurface->Wdt; squad[i*2+1] = 1.0 - squad[i*2+1] / pBackSurface->Hgt; } // Load coordinates into vertex buffer if (hVBO == 0) { glGenBuffers(1, &hVBO); glBindBuffer(GL_ARRAY_BUFFER, hVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vtxData), vtxData, GL_STREAM_DRAW); } else { glBindBuffer(GL_ARRAY_BUFFER, hVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vtxData), vtxData); } // Copy using shader C4ShaderCall Call(pShader); Call.Start(); if (Call.AllocTexUnit(C4FoWFSU_Texture)) glBindTexture(GL_TEXTURE_2D, pBackSurface->textures[0].texName); Call.SetUniformMatrix4x4(C4FoWFSU_ProjectionMatrix, projectionMatrix); glBlendFunc(GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_COLOR); float normalBlend = 1.0f / 4.0f, // Normals change quickly brightBlend = 1.0f / 16.0f; // Intensity more slowly glBlendColor(0.0f,normalBlend,normalBlend,brightBlend); glEnableVertexAttribArray(pShader->GetAttribute(C4FoWFSA_Position)); glEnableVertexAttribArray(pShader->GetAttribute(C4FoWFSA_TexCoord)); glVertexAttribPointer(pShader->GetAttribute(C4FoWFSA_Position), 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(8 * sizeof(float))); glVertexAttribPointer(pShader->GetAttribute(C4FoWFSA_TexCoord), 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(0)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(pShader->GetAttribute(C4FoWFSA_Position)); glDisableVertexAttribArray(pShader->GetAttribute(C4FoWFSA_TexCoord)); glBindBuffer(GL_ARRAY_BUFFER, 0); Call.Finish(); } // Done! glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); pDraw->RestorePrimaryClipper(); OldRegion = Region; #endif return true; } void C4FoWRegion::GetFragTransform(const C4Rect& clipRect, const C4Rect& outRect, float lightTransform[6]) const { const C4Rect& lightRect = getRegion(); const FLOAT_RECT& vpRect = ViewportRegion; C4FragTransform trans; // Clip offset assert(outRect.Hgt >= clipRect.y + clipRect.Hgt); trans.Translate(-clipRect.x, -(outRect.Hgt - clipRect.y - clipRect.Hgt)); // Clip normalization (0,0 -> 1,1) trans.Scale(1.0f / clipRect.Wdt, 1.0f / clipRect.Hgt); // Viewport/Landscape normalization trans.Scale(vpRect.right - vpRect.left, vpRect.bottom - vpRect.top); // Offset between viewport and light texture trans.Translate(vpRect.left - lightRect.x, vpRect.top - lightRect.y); // Light surface normalization trans.Scale(1.0f / pSurface->Wdt, 1.0f / pSurface->Hgt); // Light surface Y offset trans.Translate(0.0f, 1.0f - (float)(lightRect.Hgt) / (float)pSurface->Hgt); // Extract matrix trans.Get2x3(lightTransform); }