bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, bool isMemset) { if (updateVRAM_ || size == 0) { return false; } dst &= 0x3FFFFFFF; src &= 0x3FFFFFFF; VirtualFramebuffer *dstBuffer = 0; VirtualFramebuffer *srcBuffer = 0; u32 dstY = (u32)-1; u32 dstH = 0; u32 srcY = (u32)-1; u32 srcH = 0; for (size_t i = 0; i < vfbs_.size(); ++i) { VirtualFramebuffer *vfb = vfbs_[i]; if (vfb->fb_stride == 0) { continue; } const u32 vfb_address = (0x04000000 | vfb->fb_address) & 0x3FFFFFFF; const u32 vfb_size = FramebufferByteSize(vfb); const u32 vfb_bpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; const u32 vfb_byteStride = vfb->fb_stride * vfb_bpp; const int vfb_byteWidth = vfb->width * vfb_bpp; if (dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) { const u32 offset = dst - vfb_address; const u32 yOffset = offset / vfb_byteStride; if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < dstY) { dstBuffer = vfb; dstY = yOffset; dstH = size == vfb_byteWidth ? 1 : std::min((u32)size / vfb_byteStride, (u32)vfb->height); } } if (src >= vfb_address && (src + size <= vfb_address + vfb_size || src == vfb_address)) { const u32 offset = src - vfb_address; const u32 yOffset = offset / vfb_byteStride; if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < srcY) { srcBuffer = vfb; srcY = yOffset; srcH = size == vfb_byteWidth ? 1 : std::min((u32)size / vfb_byteStride, (u32)vfb->height); } else if ((offset % vfb_byteStride) == 0 && size == vfb->fb_stride && yOffset < srcY) { // Valkyrie Profile reads 512 bytes at a time, rather than 2048. So, let's whitelist fb_stride also. srcBuffer = vfb; srcY = yOffset; srcH = 1; } } } if (srcBuffer && srcY == 0 && srcH == srcBuffer->height && !dstBuffer) { // MotoGP workaround - it copies a framebuffer to memory and then displays it. // TODO: It's rare anyway, but the game could modify the RAM and then we'd display the wrong thing. // Unfortunately, that would force 1x render resolution. if (Memory::IsRAMAddress(dst)) { knownFramebufferRAMCopies_.insert(std::pair<u32, u32>(src, dst)); } } if (!useBufferedRendering_) { // If we're copying into a recently used display buf, it's probably destined for the screen. if (srcBuffer || (dstBuffer != displayFramebuf_ && dstBuffer != prevDisplayFramebuf_)) { return false; } } if (dstBuffer && srcBuffer && !isMemset) { if (srcBuffer == dstBuffer) { WARN_LOG_REPORT_ONCE(dstsrccpy, G3D, "Intra-buffer memcpy (not supported) %08x -> %08x", src, dst); } else { WARN_LOG_REPORT_ONCE(dstnotsrccpy, G3D, "Inter-buffer memcpy %08x -> %08x", src, dst); // Just do the blit! if (g_Config.bBlockTransferGPU) { BlitFramebuffer(dstBuffer, 0, dstY, srcBuffer, 0, srcY, srcBuffer->width, srcH, 0); SetColorUpdated(dstBuffer); RebindFramebuffer(); } } return false; } else if (dstBuffer) { WARN_LOG_ONCE(btucpy, G3D, "Memcpy fbo upload %08x -> %08x", src, dst); if (g_Config.bBlockTransferGPU) { FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(src); DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->width, dstH); SetColorUpdated(dstBuffer); RebindFramebuffer(); // This is a memcpy, let's still copy just in case. return false; } return false; } else if (srcBuffer) { WARN_LOG_ONCE(btdcpy, G3D, "Memcpy fbo download %08x -> %08x", src, dst); FlushBeforeCopy(); if (srcH == 0 || srcY + srcH > srcBuffer->bufferHeight) { WARN_LOG_REPORT_ONCE(btdcpyheight, G3D, "Memcpy fbo download %08x -> %08x skipped, %d+%d is taller than %d", src, dst, srcY, srcH, srcBuffer->bufferHeight); } else if (g_Config.bBlockTransferGPU && !srcBuffer->memoryUpdated) { ReadFramebufferToMemory(srcBuffer, true, 0, srcY, srcBuffer->width, srcH); } return false; } else { return false; } }
void TransformDrawEngine::ApplyDrawState(int prim) { // TODO: All this setup is soon so expensive that we'll need dirty flags, or simply do it in the command writes where we detect dirty by xoring. Silly to do all this work on every drawcall. if (gstate_c.textureChanged != TEXCHANGE_UNCHANGED && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { textureCache_->SetTexture(); gstate_c.textureChanged = TEXCHANGE_UNCHANGED; } // TODO: The top bit of the alpha channel should be written to the stencil bit somehow. This appears to require very expensive multipass rendering :( Alternatively, one could do a // single fullscreen pass that converts alpha to stencil (or 2 passes, to set both the 0 and 1 values) very easily. // Set blend bool wantBlend = !gstate.isModeClear() && gstate.isAlphaBlendEnabled(); glstate.blend.set(wantBlend); if (wantBlend) { // This can't be done exactly as there are several PSP blend modes that are impossible to do on OpenGL ES 2.0, and some even on regular OpenGL for desktop. // HOWEVER - we should be able to approximate the 2x modes in the shader, although they will clip wrongly. // Examples of seen unimplementable blend states: // Mortal Kombat Unchained: FixA=0000ff FixB=000080 FuncA=10 FuncB=10 int blendFuncA = gstate.getBlendFuncA(); int blendFuncB = gstate.getBlendFuncB(); GEBlendMode blendFuncEq = gstate.getBlendEq(); if (blendFuncA > GE_SRCBLEND_FIXA) blendFuncA = GE_SRCBLEND_FIXA; if (blendFuncB > GE_DSTBLEND_FIXB) blendFuncB = GE_DSTBLEND_FIXB; float constantAlpha = 1.0f; ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); if (gstate.isStencilTestEnabled() && replaceAlphaWithStencil == REPLACE_ALPHA_NO) { if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { constantAlpha = (float) gstate.getStencilTestRef() * (1.0f / 255.0f); } } // Shortcut by using GL_ONE where possible, no need to set blendcolor GLuint glBlendFuncA = blendFuncA == GE_SRCBLEND_FIXA ? blendColor2Func(gstate.getFixA()) : aLookup[blendFuncA]; GLuint glBlendFuncB = blendFuncB == GE_DSTBLEND_FIXB ? blendColor2Func(gstate.getFixB()) : bLookup[blendFuncB]; if (replaceAlphaWithStencil == REPLACE_ALPHA_DUALSOURCE) { glBlendFuncA = toDualSource(glBlendFuncA); glBlendFuncB = toDualSource(glBlendFuncB); } if (blendFuncA == GE_SRCBLEND_FIXA || blendFuncB == GE_DSTBLEND_FIXB) { Vec3f fixA = Vec3f::FromRGB(gstate.getFixA()); Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB != GL_INVALID_ENUM) { // Can use blendcolor trivially. const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; glstate.blendColor.set(blendColor); glBlendFuncA = GL_CONSTANT_COLOR; } else if (glBlendFuncA != GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { // Can use blendcolor trivially. const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; glstate.blendColor.set(blendColor); glBlendFuncB = GL_CONSTANT_COLOR; } else if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { if (blendColorSimilar(fixA, Vec3f::AssignToAll(constantAlpha) - fixB)) { glBlendFuncA = GL_CONSTANT_COLOR; glBlendFuncB = GL_ONE_MINUS_CONSTANT_COLOR; const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; glstate.blendColor.set(blendColor); } else if (blendColorSimilar(fixA, fixB)) { glBlendFuncA = GL_CONSTANT_COLOR; glBlendFuncB = GL_CONSTANT_COLOR; const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; glstate.blendColor.set(blendColor); } else { static bool didReportBlend = false; if (!didReportBlend) Reporting::ReportMessage("ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); didReportBlend = true; DEBUG_LOG(G3D, "ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); // Let's approximate, at least. Close is better than totally off. const bool nearZeroA = blendColorSimilar(fixA, Vec3f::AssignToAll(0.0f), 0.25f); const bool nearZeroB = blendColorSimilar(fixB, Vec3f::AssignToAll(0.0f), 0.25f); if (nearZeroA || blendColorSimilar(fixA, Vec3f::AssignToAll(1.0f), 0.25f)) { glBlendFuncA = nearZeroA ? GL_ZERO : GL_ONE; glBlendFuncB = GL_CONSTANT_COLOR; const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; glstate.blendColor.set(blendColor); // We need to pick something. Let's go with A as the fixed color. } else { glBlendFuncA = GL_CONSTANT_COLOR; glBlendFuncB = nearZeroB ? GL_ZERO : GL_ONE; const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; glstate.blendColor.set(blendColor); } } } else { // We optimized both, but that's probably not necessary, so let's pick one to be constant. // For now let's just pick whichever was fixed instead of checking error. if (blendFuncA == GE_SRCBLEND_FIXA) { glBlendFuncA = GL_CONSTANT_COLOR; const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; glstate.blendColor.set(blendColor); } else { glBlendFuncB = GL_CONSTANT_COLOR; const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; glstate.blendColor.set(blendColor); } } } else if (constantAlpha < 1.0f) { const float blendColor[4] = {1.0f, 1.0f, 1.0f, constantAlpha}; glstate.blendColor.set(blendColor); } // Some Android devices (especially Mali, it seems) composite badly if there's alpha in the backbuffer. // So in non-buffered rendering, we will simply consider the dest alpha to be zero in blending equations. #ifdef ANDROID if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) { if (glBlendFuncA == GL_DST_ALPHA) glBlendFuncA = GL_ZERO; if (glBlendFuncB == GL_DST_ALPHA) glBlendFuncB = GL_ZERO; if (glBlendFuncA == GL_ONE_MINUS_DST_ALPHA) glBlendFuncA = GL_ONE; if (glBlendFuncB == GL_ONE_MINUS_DST_ALPHA) glBlendFuncB = GL_ONE; } #endif // At this point, through all paths above, glBlendFuncA and glBlendFuncB will be set right somehow. // The stencil-to-alpha in fragment shader doesn't apply here (blending is enabled), and we shouldn't // do any blending in the alpha channel as that doesn't seem to happen on PSP. So lacking a better option, // the only value we can set alpha to here without multipass and dual source alpha is zero (by setting // the factors to zero). So let's do that. if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { // Let the fragment shader take care of it. glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ZERO); } else if (gstate.isStencilTestEnabled()) { switch (ReplaceAlphaWithStencilType()) { case STENCIL_VALUE_KEEP: glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); break; case STENCIL_VALUE_ONE: // This won't give one but it's our best shot... glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ONE); break; case STENCIL_VALUE_ZERO: glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); break; case STENCIL_VALUE_UNIFORM: // This won't give a correct value (it multiplies) but it may be better than random values. glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_CONSTANT_ALPHA, GL_ZERO); break; case STENCIL_VALUE_UNKNOWN: // For now, let's err at zero. This is INVERT or INCR/DECR. glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); break; } } else { // Retain the existing value when stencil testing is off. glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); } if (blendFuncEq == GE_BLENDMODE_ABSDIFF) { WARN_LOG_REPORT_ONCE(blendAbsdiff, G3D, "Unsupported absdiff blend mode"); } if (((blendFuncEq >= GE_BLENDMODE_MIN) && gl_extensions.EXT_blend_minmax) || gl_extensions.GLES3) { if (blendFuncEq == GE_BLENDMODE_ABSDIFF && gl_extensions.EXT_shader_framebuffer_fetch) { // Handle GE_BLENDMODE_ABSDIFF in fragment shader and turn off regular alpha blending here. glstate.blend.set(false); } else { glstate.blendEquation.set(eqLookup[blendFuncEq]); } } else { glstate.blendEquation.set(eqLookupNoMinMax[blendFuncEq]); } } bool alwaysDepthWrite = g_Config.bAlwaysDepthWrite; bool enableStencilTest = !g_Config.bDisableStencilTest; // Dither if (gstate.isDitherEnabled()) { glstate.dither.enable(); glstate.dither.set(GL_TRUE); } else glstate.dither.disable(); if (gstate.isModeClear()) { #if !defined(USING_GLES2) // Logic Ops glstate.colorLogicOp.disable(); #endif // Culling glstate.cullFace.disable(); // Depth Test glstate.depthTest.enable(); glstate.depthFunc.set(GL_ALWAYS); glstate.depthWrite.set(gstate.isClearModeDepthMask() || alwaysDepthWrite ? GL_TRUE : GL_FALSE); if (gstate.isClearModeDepthMask() || alwaysDepthWrite) { framebufferManager_->SetDepthUpdated(); } // Color Test bool colorMask = gstate.isClearModeColorMask(); bool alphaMask = gstate.isClearModeAlphaMask(); glstate.colorMask.set(colorMask, colorMask, colorMask, alphaMask); // Stencil Test if (alphaMask && enableStencilTest) { glstate.stencilTest.enable(); glstate.stencilOp.set(GL_REPLACE, GL_REPLACE, GL_REPLACE); // TODO: In clear mode, the stencil value is set to the alpha value of the vertex. // A normal clear will be 2 points, the second point has the color. // We should set "ref" to that value instead of 0. // In case of clear rectangles, we set it again once we know what the color is. glstate.stencilFunc.set(GL_ALWAYS, 255, 0xFF); } else { glstate.stencilTest.disable(); } } else { #if !defined(USING_GLES2) // Logic Ops if (gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY) { glstate.colorLogicOp.enable(); glstate.logicOp.set(logicOps[gstate.getLogicOp()]); } else { glstate.colorLogicOp.disable(); } #endif // Set cull bool cullEnabled = !gstate.isModeThrough() && prim != GE_PRIM_RECTANGLES && gstate.isCullEnabled(); if (cullEnabled) { glstate.cullFace.enable(); glstate.cullFaceMode.set(cullingMode[gstate.getCullMode()]); } else { glstate.cullFace.disable(); } // Depth Test if (gstate.isDepthTestEnabled()) { glstate.depthTest.enable(); glstate.depthFunc.set(ztests[gstate.getDepthTestFunction()]); glstate.depthWrite.set(gstate.isDepthWriteEnabled() || alwaysDepthWrite ? GL_TRUE : GL_FALSE); framebufferManager_->SetDepthUpdated(); } else { glstate.depthTest.disable(); } // PSP color/alpha mask is per bit but we can only support per byte. // But let's do that, at least. And let's try a threshold. bool rmask = (gstate.pmskc & 0xFF) < 128; bool gmask = ((gstate.pmskc >> 8) & 0xFF) < 128; bool bmask = ((gstate.pmskc >> 16) & 0xFF) < 128; bool amask = (gstate.pmska & 0xFF) < 128; // Let's not write to alpha if stencil isn't enabled. if (!gstate.isStencilTestEnabled()) { amask = false; } else { // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { amask = false; } } if (g_Config.bAlphaMaskHack) { amask = true; // Yes, this makes no sense, but it "fixes" the 3rd Birthday by popular demand. } glstate.colorMask.set(rmask, gmask, bmask, amask); // Stencil Test if (gstate.isStencilTestEnabled() && enableStencilTest) { glstate.stencilTest.enable(); glstate.stencilFunc.set(ztests[gstate.getStencilTestFunction()], gstate.getStencilTestRef(), gstate.getStencilTestMask()); glstate.stencilOp.set(stencilOps[gstate.getStencilOpSFail()], // stencil fail stencilOps[gstate.getStencilOpZFail()], // depth fail stencilOps[gstate.getStencilOpZPass()]); // depth pass } else { glstate.stencilTest.disable(); } } float renderWidthFactor, renderHeightFactor; float renderWidth, renderHeight; float renderX, renderY; bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; if (useBufferedRendering) { renderX = 0.0f; renderY = 0.0f; renderWidth = framebufferManager_->GetRenderWidth(); renderHeight = framebufferManager_->GetRenderHeight(); } else { // TODO: Aspect-ratio aware and centered float pixelW = PSP_CoreParameter().pixelWidth; float pixelH = PSP_CoreParameter().pixelHeight; CenterRect(&renderX, &renderY, &renderWidth, &renderHeight, 480, 272, pixelW, pixelH); } renderWidthFactor = (float)renderWidth / framebufferManager_->GetTargetWidth(); renderHeightFactor = (float)renderHeight / framebufferManager_->GetTargetHeight(); bool throughmode = gstate.isModeThrough(); // Scissor int scissorX1 = gstate.getScissorX1(); int scissorY1 = gstate.getScissorY1(); int scissorX2 = gstate.getScissorX2() + 1; int scissorY2 = gstate.getScissorY2() + 1; // This is a bit of a hack as the render buffer isn't always that size if (scissorX1 == 0 && scissorY1 == 0 && scissorX2 >= (int) gstate_c.curRTWidth && scissorY2 >= (int) gstate_c.curRTHeight) { glstate.scissorTest.disable(); } else { glstate.scissorTest.enable(); glstate.scissorRect.set( renderX + scissorX1 * renderWidthFactor, renderY + renderHeight - (scissorY2 * renderHeightFactor), (scissorX2 - scissorX1) * renderWidthFactor, (scissorY2 - scissorY1) * renderHeightFactor); } /* int regionX1 = gstate.region1 & 0x3FF; int regionY1 = (gstate.region1 >> 10) & 0x3FF; int regionX2 = (gstate.region2 & 0x3FF) + 1; int regionY2 = ((gstate.region2 >> 10) & 0x3FF) + 1; */ int regionX1 = 0; int regionY1 = 0; int regionX2 = gstate_c.curRTWidth; int regionY2 = gstate_c.curRTHeight; float offsetX = gstate.getOffsetX(); float offsetY = gstate.getOffsetY(); if (throughmode) { // No viewport transform here. Let's experiment with using region. glstate.viewport.set( renderX + (0 + regionX1) * renderWidthFactor, renderY + (0 - regionY1) * renderHeightFactor, (regionX2 - regionX1) * renderWidthFactor, (regionY2 - regionY1) * renderHeightFactor); glstate.depthRange.set(0.0f, 1.0f); } else { // These we can turn into a glViewport call, offset by offsetX and offsetY. Math after. float vpXa = getFloat24(gstate.viewportx1); float vpXb = getFloat24(gstate.viewportx2); float vpYa = getFloat24(gstate.viewporty1); float vpYb = getFloat24(gstate.viewporty2); // The viewport transform appears to go like this: // Xscreen = -offsetX + vpXb + vpXa * Xview // Yscreen = -offsetY + vpYb + vpYa * Yview // Zscreen = vpZb + vpZa * Zview // This means that to get the analogue glViewport we must: float vpX0 = vpXb - offsetX - vpXa; float vpY0 = vpYb - offsetY + vpYa; // Need to account for sign of Y gstate_c.vpWidth = vpXa * 2.0f; gstate_c.vpHeight = -vpYa * 2.0f; float vpWidth = fabsf(gstate_c.vpWidth); float vpHeight = fabsf(gstate_c.vpHeight); vpX0 *= renderWidthFactor; vpY0 *= renderHeightFactor; vpWidth *= renderWidthFactor; vpHeight *= renderHeightFactor; vpX0 = (vpXb - offsetX - fabsf(vpXa)) * renderWidthFactor; // Flip vpY0 to match the OpenGL coordinate system. vpY0 = renderHeight - (vpYb - offsetY + fabsf(vpYa)) * renderHeightFactor; glstate.viewport.set(vpX0 + renderX, vpY0 + renderY, vpWidth, vpHeight); // Sadly, as glViewport takes integers, we will not be able to support sub pixel offsets this way. But meh. // shaderManager_->DirtyUniform(DIRTY_PROJMATRIX); float zScale = getFloat24(gstate.viewportz1) / 65535.0f; float zOff = getFloat24(gstate.viewportz2) / 65535.0f; float depthRangeMin = zOff - zScale; float depthRangeMax = zOff + zScale; glstate.depthRange.set(depthRangeMin, depthRangeMax); } }
// TODO: This probably is not the best interface. // Also, we should try to merge this into the similar function in DrawEngineCommon. bool TransformUnit::GetCurrentSimpleVertices(int count, std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices) { // This is always for the current vertices. u16 indexLowerBound = 0; u16 indexUpperBound = count - 1; if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { const u8 *inds = Memory::GetPointer(gstate_c.indexAddr); const u16 *inds16 = (const u16 *)inds; const u32 *inds32 = (const u32 *)inds; if (inds) { GetIndexBounds(inds, count, gstate.vertType, &indexLowerBound, &indexUpperBound); indices.resize(count); switch (gstate.vertType & GE_VTYPE_IDX_MASK) { case GE_VTYPE_IDX_8BIT: for (int i = 0; i < count; ++i) { indices[i] = inds[i]; } break; case GE_VTYPE_IDX_16BIT: for (int i = 0; i < count; ++i) { indices[i] = inds16[i]; } break; case GE_VTYPE_IDX_32BIT: WARN_LOG_REPORT_ONCE(simpleIndexes32, G3D, "SimpleVertices: Decoding 32-bit indexes"); for (int i = 0; i < count; ++i) { // These aren't documented and should be rare. Let's bounds check each one. if (inds32[i] != (u16)inds32[i]) { ERROR_LOG_REPORT_ONCE(simpleIndexes32Bounds, G3D, "SimpleVertices: Index outside 16-bit range"); } indices[i] = (u16)inds32[i]; } break; } } else { indices.clear(); } } else { indices.clear(); } static std::vector<u32> temp_buffer; static std::vector<SimpleVertex> simpleVertices; temp_buffer.resize(65536 * 24 / sizeof(u32)); simpleVertices.resize(indexUpperBound + 1); VertexDecoder vdecoder; VertexDecoderOptions options{}; vdecoder.SetVertexType(gstate.vertType, options); DrawEngineCommon::NormalizeVertices((u8 *)(&simpleVertices[0]), (u8 *)(&temp_buffer[0]), Memory::GetPointer(gstate_c.vertexAddr), &vdecoder, indexLowerBound, indexUpperBound, gstate.vertType); float world[16]; float view[16]; float worldview[16]; float worldviewproj[16]; ConvertMatrix4x3To4x4(world, gstate.worldMatrix); ConvertMatrix4x3To4x4(view, gstate.viewMatrix); Matrix4ByMatrix4(worldview, world, view); Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix); vertices.resize(indexUpperBound + 1); for (int i = indexLowerBound; i <= indexUpperBound; ++i) { const SimpleVertex &vert = simpleVertices[i]; if (gstate.isModeThrough()) { if (gstate.vertType & GE_VTYPE_TC_MASK) { vertices[i].u = vert.uv[0]; vertices[i].v = vert.uv[1]; } else { vertices[i].u = 0.0f; vertices[i].v = 0.0f; } vertices[i].x = vert.pos.x; vertices[i].y = vert.pos.y; vertices[i].z = vert.pos.z; if (gstate.vertType & GE_VTYPE_COL_MASK) { memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c)); } else { memset(vertices[i].c, 0, sizeof(vertices[i].c)); } } else { float clipPos[4]; Vec3ByMatrix44(clipPos, vert.pos.AsArray(), worldviewproj); ScreenCoords screenPos = ClipToScreen(clipPos); DrawingCoords drawPos = ScreenToDrawing(screenPos); if (gstate.vertType & GE_VTYPE_TC_MASK) { vertices[i].u = vert.uv[0] * (float)gstate.getTextureWidth(0); vertices[i].v = vert.uv[1] * (float)gstate.getTextureHeight(0); } else { vertices[i].u = 0.0f; vertices[i].v = 0.0f; } vertices[i].x = drawPos.x; vertices[i].y = drawPos.y; vertices[i].z = drawPos.z; if (gstate.vertType & GE_VTYPE_COL_MASK) { memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c)); } else { memset(vertices[i].c, 0, sizeof(vertices[i].c)); } } } // The GE debugger expects these to be set. gstate_c.curTextureWidth = gstate.getTextureWidth(0); gstate_c.curTextureHeight = gstate.getTextureHeight(0); return true; }
void FramebufferManagerCommon::DoSetRenderFrameBuffer() { gstate_c.framebufChanged = false; // Get parameters const u32 fb_address = gstate.getFrameBufRawAddress(); const int fb_stride = gstate.FrameBufStride(); const u32 z_address = gstate.getDepthBufRawAddress(); const int z_stride = gstate.DepthBufStride(); GEBufferFormat fmt = gstate.FrameBufFormat(); // As there are no clear "framebuffer width" and "framebuffer height" registers, // we need to infer the size of the current framebuffer somehow. int drawing_width, drawing_height; EstimateDrawingSize(drawing_width, drawing_height); gstate_c.cutRTOffsetX = 0; bool vfbFormatChanged = false; // Find a matching framebuffer VirtualFramebuffer *vfb = 0; for (size_t i = 0; i < vfbs_.size(); ++i) { VirtualFramebuffer *v = vfbs_[i]; if (v->fb_address == fb_address) { vfb = v; // Update fb stride in case it changed if (vfb->fb_stride != fb_stride || vfb->format != fmt) { vfbFormatChanged = true; vfb->fb_stride = fb_stride; vfb->format = fmt; } // In throughmode, a higher height could be used. Let's avoid shrinking the buffer. if (gstate.isModeThrough() && (int)vfb->width < fb_stride) { vfb->width = std::max((int)vfb->width, drawing_width); vfb->height = std::max((int)vfb->height, drawing_height); } else { vfb->width = drawing_width; vfb->height = drawing_height; } break; } else if (v->fb_address < fb_address && v->fb_address + v->fb_stride * 4 > fb_address) { // Possibly a render-to-offset. const u32 bpp = v->format == GE_FORMAT_8888 ? 4 : 2; const int x_offset = (fb_address - v->fb_address) / bpp; if (v->format == fmt && v->fb_stride == fb_stride && x_offset < fb_stride && v->height >= drawing_height) { WARN_LOG_REPORT_ONCE(renderoffset, HLE, "Rendering to framebuffer offset: %08x +%dx%d", v->fb_address, x_offset, 0); vfb = v; gstate_c.cutRTOffsetX = x_offset; vfb->width = std::max((int)vfb->width, x_offset + drawing_width); // To prevent the newSize code from being confused. drawing_width += x_offset; break; } } } if (vfb) { if ((drawing_width != vfb->bufferWidth || drawing_height != vfb->bufferHeight)) { // Even if it's not newly wrong, if this is larger we need to resize up. if (vfb->width > vfb->bufferWidth || vfb->height > vfb->bufferHeight) { ResizeFramebufFBO(vfb, vfb->width, vfb->height); } else if (vfb->newWidth != drawing_width || vfb->newHeight != drawing_height) { // If it's newly wrong, or changing every frame, just keep track. vfb->newWidth = drawing_width; vfb->newHeight = drawing_height; vfb->lastFrameNewSize = gpuStats.numFlips; } else if (vfb->lastFrameNewSize + FBO_OLD_AGE < gpuStats.numFlips) { // Okay, it's changed for a while (and stayed that way.) Let's start over. // But only if we really need to, to avoid blinking. bool needsRecreate = vfb->bufferWidth > fb_stride; needsRecreate = needsRecreate || vfb->newWidth > vfb->bufferWidth || vfb->newWidth * 2 < vfb->bufferWidth; needsRecreate = needsRecreate || vfb->newHeight > vfb->bufferHeight || vfb->newHeight * 2 < vfb->bufferHeight; if (needsRecreate) { ResizeFramebufFBO(vfb, vfb->width, vfb->height, true); } } } else { // It's not different, let's keep track of that too. vfb->lastFrameNewSize = gpuStats.numFlips; } } float renderWidthFactor = (float)PSP_CoreParameter().renderWidth / 480.0f; float renderHeightFactor = (float)PSP_CoreParameter().renderHeight / 272.0f; if (hackForce04154000Download_ && fb_address == 0x00154000) { renderWidthFactor = 1.0; renderHeightFactor = 1.0; } // None found? Create one. if (!vfb) { vfb = new VirtualFramebuffer(); vfb->fbo = 0; vfb->fb_address = fb_address; vfb->fb_stride = fb_stride; vfb->z_address = z_address; vfb->z_stride = z_stride; vfb->width = drawing_width; vfb->height = drawing_height; vfb->newWidth = drawing_width; vfb->newHeight = drawing_height; vfb->lastFrameNewSize = gpuStats.numFlips; vfb->renderWidth = (u16)(drawing_width * renderWidthFactor); vfb->renderHeight = (u16)(drawing_height * renderHeightFactor); vfb->bufferWidth = drawing_width; vfb->bufferHeight = drawing_height; vfb->format = fmt; vfb->drawnWidth = 0; vfb->drawnHeight = 0; vfb->drawnFormat = fmt; vfb->usageFlags = FB_USAGE_RENDERTARGET; SetColorUpdated(vfb); vfb->depthUpdated = false; u32 byteSize = FramebufferByteSize(vfb); u32 fb_address_mem = (fb_address & 0x3FFFFFFF) | 0x04000000; if (Memory::IsVRAMAddress(fb_address_mem) && fb_address_mem + byteSize > framebufRangeEnd_) { framebufRangeEnd_ = fb_address_mem + byteSize; } ResizeFramebufFBO(vfb, drawing_width, drawing_height, true); NotifyRenderFramebufferCreated(vfb); INFO_LOG(SCEGE, "Creating FBO for %08x : %i x %i x %i", vfb->fb_address, vfb->width, vfb->height, vfb->format); vfb->last_frame_render = gpuStats.numFlips; vfb->last_frame_used = 0; vfb->last_frame_attached = 0; vfb->last_frame_displayed = 0; frameLastFramebufUsed_ = gpuStats.numFlips; vfbs_.push_back(vfb); currentRenderVfb_ = vfb; if (useBufferedRendering_ && !updateVRAM_ && !g_Config.bDisableSlowFramebufEffects) { gpu->PerformMemoryUpload(fb_address_mem, byteSize); NotifyStencilUpload(fb_address_mem, byteSize, true); // TODO: Is it worth trying to upload the depth buffer? } // Let's check for depth buffer overlap. Might be interesting. bool sharingReported = false; bool writingDepth = true; // Technically, it may write depth later, but we're trying to detect it only when it's really true. if (gstate.isModeClear()) { writingDepth = !gstate.isClearModeDepthMask() && gstate.isDepthWriteEnabled(); } else { writingDepth = gstate.isDepthWriteEnabled(); } for (size_t i = 0, end = vfbs_.size(); i < end; ++i) { if (vfbs_[i]->z_stride != 0 && fb_address == vfbs_[i]->z_address) { // If it's clearing it, most likely it just needs more video memory. // Technically it could write something interesting and the other might not clear, but that's not likely. if (!gstate.isModeClear() || !gstate.isClearModeColorMask() || !gstate.isClearModeAlphaMask()) { if (fb_address != z_address && vfbs_[i]->fb_address != vfbs_[i]->z_address) { WARN_LOG_REPORT(SCEGE, "FBO created from existing depthbuffer as color, %08x/%08x and %08x/%08x", fb_address, z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); } } } else if (z_stride != 0 && z_address == vfbs_[i]->fb_address) { // If it's clearing it, then it's probably just the reverse of the above case. if (writingDepth) { WARN_LOG_REPORT(SCEGE, "FBO using existing buffer as depthbuffer, %08x/%08x and %08x/%08x", fb_address, z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); } } else if (vfbs_[i]->z_stride != 0 && z_address == vfbs_[i]->z_address && fb_address != vfbs_[i]->fb_address && !sharingReported) { // This happens a lot, but virtually always it's cleared. // It's possible the other might not clear, but when every game is reported it's not useful. if (writingDepth) { WARN_LOG_REPORT(SCEGE, "FBO reusing depthbuffer, %08x/%08x and %08x/%08x", fb_address, z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); sharingReported = true; } } } // We already have it! } else if (vfb != currentRenderVfb_) { // Use it as a render target. DEBUG_LOG(SCEGE, "Switching render target to FBO for %08x: %i x %i x %i ", vfb->fb_address, vfb->width, vfb->height, vfb->format); vfb->usageFlags |= FB_USAGE_RENDERTARGET; vfb->last_frame_render = gpuStats.numFlips; frameLastFramebufUsed_ = gpuStats.numFlips; vfb->dirtyAfterDisplay = true; if ((gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) vfb->reallyDirtyAfterDisplay = true; VirtualFramebuffer *prev = currentRenderVfb_; currentRenderVfb_ = vfb; NotifyRenderFramebufferSwitched(prev, vfb); } else { vfb->last_frame_render = gpuStats.numFlips; frameLastFramebufUsed_ = gpuStats.numFlips; vfb->dirtyAfterDisplay = true; if ((gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) vfb->reallyDirtyAfterDisplay = true; NotifyRenderFramebufferUpdated(vfb, vfbFormatChanged); } gstate_c.curRTWidth = vfb->width; gstate_c.curRTHeight = vfb->height; gstate_c.curRTRenderWidth = vfb->renderWidth; gstate_c.curRTRenderHeight = vfb->renderHeight; }
// Missing: Z depth range void GenerateFragmentShader(char *buffer) { char *p = buffer; bool highpFog = false; #if defined(GLSL_ES_1_0) WRITE(p, "#version 100\n"); // GLSL ES 1.0 WRITE(p, "precision lowp float;\n"); // PowerVR needs highp to do the fog in MHU correctly. // Others don't, and some can't handle highp in the fragment shader. highpFog = gl_extensions.gpuVendor == GPU_VENDOR_POWERVR; #elif !defined(FORCE_OPENGL_2_0) WRITE(p, "#version 110\n"); // Remove lowp/mediump in non-mobile implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #else // Remove lowp/mediump in non-mobile implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #endif bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear() && !g_Config.bDisableAlphaTest; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool enableColorDoubling = gstate.isColorDoublingEnabled(); // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); bool stencilToAlpha = !gstate.isModeClear() && gstate.isStencilTestEnabled() && !gstate.isAlphaBlendEnabled(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); if (enableAlphaTest || enableColorTest) { WRITE(p, "uniform vec4 u_alphacolorref;\n"); } if (stencilToAlpha && ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { WRITE(p, "uniform float u_stencilReplaceValue;\n"); } if (gstate.isTextureMapEnabled() && gstate.getTextureFunction() == GE_TEXFUNC_BLEND) WRITE(p, "uniform lowp vec3 u_texenv;\n"); WRITE(p, "varying lowp vec4 v_color0;\n"); if (lmode) WRITE(p, "varying lowp vec3 v_color1;\n"); if (enableFog) { WRITE(p, "uniform lowp vec3 u_fogcolor;\n"); WRITE(p, "varying %s float v_fogdepth;\n", highpFog ? "highp" : "mediump"); } if (doTexture) { if (doTextureProjection) WRITE(p, "varying mediump vec3 v_texcoord;\n"); else WRITE(p, "varying mediump vec2 v_texcoord;\n"); } if (enableAlphaTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); } if (enableColorTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); } WRITE(p, "void main() {\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " gl_FragColor = v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " vec4 t = texture2DProj(tex, v_texcoord);\n"); } else { WRITE(p, " vec4 t = texture2D(tex, v_texcoord);\n"); } WRITE(p, " vec4 p = v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " vec4 v = v_color0 %s;\n", secondary); } if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) { // Work around bad PVR driver problem where equality check + discard just doesn't work. if (alphaTestFunc != 3) WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } else { WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } } } // TODO: Before or after the color test? if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense WARN_LOG_REPORT_ONCE(colortest, G3D, "Color test function : %s", colorTestFuncs[colorTestFunc]); u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); else WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); } } if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " gl_FragColor = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } else { WRITE(p, " gl_FragColor = v;\n"); } } if (stencilToAlpha) { switch (ReplaceAlphaWithStencilType()) { case STENCIL_VALUE_UNIFORM: WRITE(p, " gl_FragColor.a = u_stencilReplaceValue;\n"); break; case STENCIL_VALUE_ZERO: WRITE(p, " gl_FragColor.a = 0.0;\n"); break; case STENCIL_VALUE_ONE: WRITE(p, " gl_FragColor.a = 1.0;\n"); break; case STENCIL_VALUE_UNKNOWN: // Maybe we should even mask away alpha using glColorMask and not change it at all? We do get here // if the stencil mode is KEEP for example. WRITE(p, " gl_FragColor.a = 0.0;\n"); break; case STENCIL_VALUE_KEEP: // Do nothing. We'll mask out the alpha using color mask. break; } } #ifdef DEBUG_SHADER if (doTexture) { WRITE(p, " gl_FragColor = texture2D(tex, v_texcoord.xy);\n"); WRITE(p, " gl_FragColor += vec4(0.3,0,0.3,0.3);\n"); } else { WRITE(p, " gl_FragColor = vec4(1,0,1,1);\n"); } #endif WRITE(p, "}\n"); }
int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int videoPixelMode, int xpos, int ypos, int width, int height) { if (!Memory::IsValidAddress(bufferPtr) || frameWidth > 2048) { // Clearly invalid values. Let's just not. ERROR_LOG_REPORT(ME, "Ignoring invalid video decode address %08x/%x", bufferPtr, frameWidth); return 0; } u8 *buffer = Memory::GetPointer(bufferPtr); #ifdef USE_FFMPEG if (!m_pFrame || !m_pFrameRGB) return 0; // lock the image size u8 *imgbuf = buffer; const u8 *data = m_pFrameRGB->data[0]; int videoLineSize = 0; switch (videoPixelMode) { case GE_CMODE_32BIT_ABGR8888: videoLineSize = frameWidth * sizeof(u32); break; case GE_CMODE_16BIT_BGR5650: case GE_CMODE_16BIT_ABGR5551: case GE_CMODE_16BIT_ABGR4444: videoLineSize = frameWidth * sizeof(u16); break; } int videoImageSize = videoLineSize * height; bool swizzle = Memory::IsVRAMAddress(bufferPtr) && (bufferPtr & 0x00200000) == 0x00200000; if (swizzle) { imgbuf = new u8[videoImageSize]; } if (width > m_desWidth - xpos) width = m_desWidth - xpos; if (height > m_desHeight - ypos) height = m_desHeight - ypos; switch (videoPixelMode) { case GE_CMODE_32BIT_ABGR8888: data += (ypos * m_desWidth + xpos) * sizeof(u32); for (int y = 0; y < height; y++) { writeVideoLineRGBA(imgbuf, data, width); data += m_desWidth * sizeof(u32); imgbuf += videoLineSize; #ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u32), true, width * sizeof(u32), currentMIPS->pc); #endif } break; case GE_CMODE_16BIT_BGR5650: data += (ypos * m_desWidth + xpos) * sizeof(u16); for (int y = 0; y < height; y++) { writeVideoLineABGR5650(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; #ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); #endif } break; case GE_CMODE_16BIT_ABGR5551: data += (ypos * m_desWidth + xpos) * sizeof(u16); for (int y = 0; y < height; y++) { writeVideoLineABGR5551(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; #ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); #endif } break; case GE_CMODE_16BIT_ABGR4444: data += (ypos * m_desWidth + xpos) * sizeof(u16); for (int y = 0; y < height; y++) { writeVideoLineABGR4444(imgbuf, data, width); data += m_desWidth * sizeof(u16); imgbuf += videoLineSize; #ifndef MOBILE_DEVICE CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc); #endif } break; default: ERROR_LOG_REPORT(ME, "Unsupported video pixel format %d", videoPixelMode); break; } if (swizzle) { WARN_LOG_REPORT_ONCE(vidswizzle, ME, "Swizzling Video with range"); const u32 pitch = videoLineSize / 4; const int bxc = videoLineSize / 16; int byc = (height + 7) / 8; if (byc == 0) byc = 1; DoSwizzleTex16((const u32 *)imgbuf, buffer, bxc, byc, pitch, videoLineSize); delete [] imgbuf; } return videoImageSize; #endif // USE_FFMPEG return 0; }
VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const FramebufferHeuristicParams ¶ms, u32 skipDrawReason) { gstate_c.framebufChanged = false; // Collect all parameters. This whole function has really become a cesspool of heuristics... // but it appears that's what it takes, unless we emulate VRAM layout more accurately somehow. // As there are no clear "framebuffer width" and "framebuffer height" registers, // we need to infer the size of the current framebuffer somehow. int drawing_width, drawing_height; EstimateDrawingSize(params.fb_address, params.fmt, params.viewportWidth, params.viewportHeight, params.regionWidth, params.regionHeight, params.scissorWidth, params.scissorHeight, std::max(params.fb_stride, 4), drawing_width, drawing_height); gstate_c.curRTOffsetX = 0; bool vfbFormatChanged = false; // Find a matching framebuffer VirtualFramebuffer *vfb = 0; for (size_t i = 0; i < vfbs_.size(); ++i) { VirtualFramebuffer *v = vfbs_[i]; if (v->fb_address == params.fb_address) { vfb = v; // Update fb stride in case it changed if (vfb->fb_stride != params.fb_stride || vfb->format != params.fmt) { vfbFormatChanged = true; vfb->fb_stride = params.fb_stride; vfb->format = params.fmt; } // Keep track, but this isn't really used. vfb->z_stride = params.z_stride; // Heuristic: In throughmode, a higher height could be used. Let's avoid shrinking the buffer. if (params.isModeThrough && (int)vfb->width < params.fb_stride) { vfb->width = std::max((int)vfb->width, drawing_width); vfb->height = std::max((int)vfb->height, drawing_height); } else { vfb->width = drawing_width; vfb->height = drawing_height; } break; } else if (v->fb_address < params.fb_address && v->fb_address + v->fb_stride * 4 > params.fb_address) { // Possibly a render-to-offset. const u32 bpp = v->format == GE_FORMAT_8888 ? 4 : 2; const int x_offset = (params.fb_address - v->fb_address) / bpp; if (v->format == params.fmt && v->fb_stride == params.fb_stride && x_offset < params.fb_stride && v->height >= drawing_height) { WARN_LOG_REPORT_ONCE(renderoffset, HLE, "Rendering to framebuffer offset: %08x +%dx%d", v->fb_address, x_offset, 0); vfb = v; gstate_c.curRTOffsetX = x_offset; vfb->width = std::max((int)vfb->width, x_offset + drawing_width); // To prevent the newSize code from being confused. drawing_width += x_offset; break; } } } if (vfb) { if ((drawing_width != vfb->bufferWidth || drawing_height != vfb->bufferHeight)) { // Even if it's not newly wrong, if this is larger we need to resize up. if (vfb->width > vfb->bufferWidth || vfb->height > vfb->bufferHeight) { ResizeFramebufFBO(vfb, vfb->width, vfb->height); } else if (vfb->newWidth != drawing_width || vfb->newHeight != drawing_height) { // If it's newly wrong, or changing every frame, just keep track. vfb->newWidth = drawing_width; vfb->newHeight = drawing_height; vfb->lastFrameNewSize = gpuStats.numFlips; } else if (vfb->lastFrameNewSize + FBO_OLD_AGE < gpuStats.numFlips) { // Okay, it's changed for a while (and stayed that way.) Let's start over. // But only if we really need to, to avoid blinking. bool needsRecreate = vfb->bufferWidth > params.fb_stride; needsRecreate = needsRecreate || vfb->newWidth > vfb->bufferWidth || vfb->newWidth * 2 < vfb->bufferWidth; needsRecreate = needsRecreate || vfb->newHeight > vfb->bufferHeight || vfb->newHeight * 2 < vfb->bufferHeight; if (needsRecreate) { ResizeFramebufFBO(vfb, vfb->width, vfb->height, true); // Let's discard this information, might be wrong now. vfb->safeWidth = 0; vfb->safeHeight = 0; } else { // Even though we won't resize it, let's at least change the size params. vfb->width = drawing_width; vfb->height = drawing_height; } } } else { // It's not different, let's keep track of that too. vfb->lastFrameNewSize = gpuStats.numFlips; } } float renderWidthFactor = renderWidth_ / 480.0f; float renderHeightFactor = renderHeight_ / 272.0f; if (hackForce04154000Download_ && params.fb_address == 0x00154000) { renderWidthFactor = 1.0; renderHeightFactor = 1.0; } // None found? Create one. if (!vfb) { vfb = new VirtualFramebuffer(); memset(vfb, 0, sizeof(VirtualFramebuffer)); vfb->fbo = nullptr; vfb->fb_address = params.fb_address; vfb->fb_stride = params.fb_stride; vfb->z_address = params.z_address; vfb->z_stride = params.z_stride; vfb->width = drawing_width; vfb->height = drawing_height; vfb->newWidth = drawing_width; vfb->newHeight = drawing_height; vfb->lastFrameNewSize = gpuStats.numFlips; vfb->renderWidth = (u16)(drawing_width * renderWidthFactor); vfb->renderHeight = (u16)(drawing_height * renderHeightFactor); vfb->bufferWidth = drawing_width; vfb->bufferHeight = drawing_height; vfb->format = params.fmt; vfb->drawnFormat = params.fmt; vfb->usageFlags = FB_USAGE_RENDERTARGET; SetColorUpdated(vfb, skipDrawReason); vfb->depthUpdated = false; u32 byteSize = FramebufferByteSize(vfb); u32 fb_address_mem = (params.fb_address & 0x3FFFFFFF) | 0x04000000; if (Memory::IsVRAMAddress(fb_address_mem) && fb_address_mem + byteSize > framebufRangeEnd_) { framebufRangeEnd_ = fb_address_mem + byteSize; } ResizeFramebufFBO(vfb, drawing_width, drawing_height, true); NotifyRenderFramebufferCreated(vfb); INFO_LOG(SCEGE, "Creating FBO for %08x : %i x %i x %i", vfb->fb_address, vfb->width, vfb->height, vfb->format); vfb->last_frame_render = gpuStats.numFlips; frameLastFramebufUsed_ = gpuStats.numFlips; vfbs_.push_back(vfb); currentRenderVfb_ = vfb; if (useBufferedRendering_ && !updateVRAM_ && !g_Config.bDisableSlowFramebufEffects) { gpu->PerformMemoryUpload(fb_address_mem, byteSize); NotifyStencilUpload(fb_address_mem, byteSize, true); // TODO: Is it worth trying to upload the depth buffer? } // Let's check for depth buffer overlap. Might be interesting. bool sharingReported = false; for (size_t i = 0, end = vfbs_.size(); i < end; ++i) { if (vfbs_[i]->z_stride != 0 && params.fb_address == vfbs_[i]->z_address) { // If it's clearing it, most likely it just needs more video memory. // Technically it could write something interesting and the other might not clear, but that's not likely. if (params.isDrawing) { if (params.fb_address != params.z_address && vfbs_[i]->fb_address != vfbs_[i]->z_address) { WARN_LOG_REPORT(SCEGE, "FBO created from existing depthbuffer as color, %08x/%08x and %08x/%08x", params.fb_address, params.z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); } } } else if (params.z_stride != 0 && params.z_address == vfbs_[i]->fb_address) { // If it's clearing it, then it's probably just the reverse of the above case. if (params.isWritingDepth) { WARN_LOG_REPORT(SCEGE, "FBO using existing buffer as depthbuffer, %08x/%08x and %08x/%08x", params.fb_address, params.z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); } } else if (vfbs_[i]->z_stride != 0 && params.z_address == vfbs_[i]->z_address && params.fb_address != vfbs_[i]->fb_address && !sharingReported) { // This happens a lot, but virtually always it's cleared. // It's possible the other might not clear, but when every game is reported it's not useful. if (params.isWritingDepth) { WARN_LOG_REPORT(SCEGE, "FBO reusing depthbuffer, %08x/%08x and %08x/%08x", params.fb_address, params.z_address, vfbs_[i]->fb_address, vfbs_[i]->z_address); sharingReported = true; } } } // We already have it! } else if (vfb != currentRenderVfb_) { // Use it as a render target. DEBUG_LOG(SCEGE, "Switching render target to FBO for %08x: %i x %i x %i ", vfb->fb_address, vfb->width, vfb->height, vfb->format); vfb->usageFlags |= FB_USAGE_RENDERTARGET; vfb->last_frame_render = gpuStats.numFlips; frameLastFramebufUsed_ = gpuStats.numFlips; vfb->dirtyAfterDisplay = true; if ((skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) vfb->reallyDirtyAfterDisplay = true; VirtualFramebuffer *prev = currentRenderVfb_; currentRenderVfb_ = vfb; NotifyRenderFramebufferSwitched(prev, vfb, params.isClearingDepth); } else { vfb->last_frame_render = gpuStats.numFlips; frameLastFramebufUsed_ = gpuStats.numFlips; vfb->dirtyAfterDisplay = true; if ((skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) vfb->reallyDirtyAfterDisplay = true; NotifyRenderFramebufferUpdated(vfb, vfbFormatChanged); } gstate_c.curRTWidth = vfb->width; gstate_c.curRTHeight = vfb->height; gstate_c.curRTRenderWidth = vfb->renderWidth; gstate_c.curRTRenderHeight = vfb->renderHeight; return vfb; }
// Missing: Z depth range void GenerateFragmentShader(char *buffer) { char *p = buffer; // In GLSL ES 3.0, you use "in" variables instead of varying. bool glslES30 = false; const char *varying = "varying"; const char *fragColor0 = "gl_FragColor"; const char *texture = "texture2D"; bool highpFog = false; #if defined(USING_GLES2) // Let's wait until we have a real use for this. // ES doesn't support dual source alpha :( if (gl_extensions.GLES3) { WRITE(p, "#version 300 es\n"); // GLSL ES 1.0 fragColor0 = "fragColor0"; texture = "texture"; glslES30 = true; } else { WRITE(p, "#version 100\n"); // GLSL ES 1.0 } WRITE(p, "precision lowp float;\n"); // PowerVR needs highp to do the fog in MHU correctly. // Others don't, and some can't handle highp in the fragment shader. highpFog = gl_extensions.gpuVendor == GPU_VENDOR_POWERVR; // GL_NV_shader_framebuffer_fetch available on mobile platform and ES 2.0 only but not desktop if (gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, " #extension GL_NV_shader_framebuffer_fetch : require\n"); } #elif !defined(FORCE_OPENGL_2_0) if (gl_extensions.VersionGEThan(3, 3, 0)) { fragColor0 = "fragColor0"; texture = "texture"; glslES30 = true; WRITE(p, "#version 330\n"); } else if (gl_extensions.VersionGEThan(3, 0, 0)) { fragColor0 = "fragColor0"; WRITE(p, "#version 130\n"); // Remove lowp/mediump in non-mobile non-glsl 3 implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); } else { WRITE(p, "#version 110\n"); // Remove lowp/mediump in non-mobile non-glsl 3 implementations WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); } #else // Need to remove lowp/mediump for Mac WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); WRITE(p, "#define highp\n"); #endif if (glslES30) { varying = "in"; } bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear(); bool alphaTestAgainstZero = gstate.getAlphaTestRef() == 0; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); bool alphaToColorDoubling = AlphaToColorDoubling(); bool enableColorDoubling = (gstate.isColorDoublingEnabled() && gstate.isTextureMapEnabled()) || alphaToColorDoubling; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. bool enableAlphaDoubling = !alphaToColorDoubling && CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(); if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) doTextureAlpha = false; if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); if (ShouldUseShaderBlending() && !gstate.isModeClear()) { if (!gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, "uniform sampler2D fbotex;\n"); } if (gstate.getBlendFuncA() == GE_SRCBLEND_FIXA) { WRITE(p, "uniform vec3 u_blendFixA;\n"); } if (gstate.getBlendFuncB() == GE_DSTBLEND_FIXB) { WRITE(p, "uniform vec3 u_blendFixB;\n"); } } if (enableAlphaTest || enableColorTest) { WRITE(p, "uniform vec4 u_alphacolorref;\n"); } if (stencilToAlpha && ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { WRITE(p, "uniform float u_stencilReplaceValue;\n"); } if (gstate.isTextureMapEnabled() && gstate.getTextureFunction() == GE_TEXFUNC_BLEND) WRITE(p, "uniform vec3 u_texenv;\n"); WRITE(p, "%s vec4 v_color0;\n", varying); if (lmode) WRITE(p, "%s vec3 v_color1;\n", varying); if (enableFog) { WRITE(p, "uniform vec3 u_fogcolor;\n"); WRITE(p, "%s %s float v_fogdepth;\n", varying, highpFog ? "highp" : "mediump"); } if (doTexture) { if (doTextureProjection) WRITE(p, "%s mediump vec3 v_texcoord;\n", varying); else WRITE(p, "%s mediump vec2 v_texcoord;\n", varying); } if (enableAlphaTest && !alphaTestAgainstZero) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); } if (enableColorTest) { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); else WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); } if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { WRITE(p, "out vec4 fragColor0;\n"); WRITE(p, "out vec4 fragColor1;\n"); } else if (!strcmp(fragColor0, "fragColor0")) { WRITE(p, "out vec4 fragColor0;\n"); } WRITE(p, "void main() {\n"); if (gstate.isModeClear()) { // Clear mode does not allow any fancy shading. WRITE(p, " vec4 v = v_color0;\n"); } else { const char *secondary = ""; // Secondary color for specular on top of texture if (lmode) { WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); secondary = " + s"; } else { secondary = ""; } if (gstate.isTextureMapEnabled()) { if (doTextureProjection) { WRITE(p, " vec4 t = %sProj(tex, v_texcoord);\n", texture); } else { WRITE(p, " vec4 t = %s(tex, v_texcoord);\n", texture); } WRITE(p, " vec4 p = v_color0;\n"); if (doTextureAlpha) { // texfmt == RGBA switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = p * t%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = t%s;\n", secondary); break; case GE_TEXFUNC_ADD: case GE_TEXFUNC_UNKNOWN1: case GE_TEXFUNC_UNKNOWN2: case GE_TEXFUNC_UNKNOWN3: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } else { // texfmt == RGB switch (gstate.getTextureFunction()) { case GE_TEXFUNC_MODULATE: WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_DECAL: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_BLEND: WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; case GE_TEXFUNC_REPLACE: WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); break; case GE_TEXFUNC_ADD: case GE_TEXFUNC_UNKNOWN1: case GE_TEXFUNC_UNKNOWN2: case GE_TEXFUNC_UNKNOWN3: WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; default: WRITE(p, " vec4 v = p;\n"); break; } } } else { // No texture mapping WRITE(p, " vec4 v = v_color0 %s;\n", secondary); } if (enableAlphaTest) { GEComparison alphaTestFunc = gstate.getAlphaTestFunction(); const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense if (alphaTestFuncs[alphaTestFunc][0] != '#') { if (alphaTestAgainstZero) { // When testing against 0 (extremely common), we can avoid some math. // 0.002 is approximately half of 1.0 / 255.0. if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { WRITE(p, " if (v.a < 0.002) discard;\n"); } else { // Anything else is a test for == 0. Happens sometimes, actually... WRITE(p, " if (v.a > 0.002) discard;\n"); } } else if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) { // Work around bad PVR driver problem where equality check + discard just doesn't work. if (alphaTestFunc != GE_COMP_NOTEQUAL) WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } else { WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); } } } if (enableColorTest) { GEComparison colorTestFunc = gstate.getColorTestFunction(); const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense WARN_LOG_REPORT_ONCE(colortest, G3D, "Color test function : %s", colorTestFuncs[colorTestFunc]); u32 colorTestMask = gstate.getColorTestMask(); if (colorTestFuncs[colorTestFunc][0] != '#') { if (gl_extensions.gpuVendor == GPU_VENDOR_POWERVR) WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); else WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) discard;\n", colorTestFuncs[colorTestFunc]); } } // Color doubling happens after the color test. if (enableColorDoubling && enableAlphaDoubling) { WRITE(p, " v = v * 2.0;\n"); } else if (enableColorDoubling) { WRITE(p, " v.rgb = v.rgb * 2.0;\n"); } else if (enableAlphaDoubling) { WRITE(p, " v.a = v.a * 2.0;\n"); } if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " v = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); // WRITE(p, " v.x = v_depth;\n"); } if (ShouldUseShaderBlending()) { // If we have NV_shader_framebuffer_fetch / EXT_shader_framebuffer_fetch, we skip the blit. // We can just read the prev value more directly. // TODO: EXT_shader_framebuffer_fetch on iOS 6, possibly others. if (gl_extensions.NV_shader_framebuffer_fetch) { WRITE(p, " lowp vec4 destColor = gl_LastFragData[0];\n"); } else { WRITE(p, " lowp vec4 destColor = texelFetch(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n"); } GEBlendSrcFactor funcA = gstate.getBlendFuncA(); GEBlendDstFactor funcB = gstate.getBlendFuncB(); GEBlendMode eq = gstate.getBlendEq(); const char *srcFactor = "vec3(1.0)"; const char *dstFactor = "vec3(0.0)"; switch (funcA) { case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(vec3(1.0) - destColor.rgb)"; break; case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; case GE_SRCBLEND_DSTALPHA: srcFactor = "vec3(destColor.a)"; break; case GE_SRCBLEND_INVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a)"; break; case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; // TODO: Double inverse, or inverse double? Following softgpu for now... case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "vec3(destColor.a * 2.0)"; break; case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a * 2.0)"; break; case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; } switch (funcB) { case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(vec3(1.0) - v.rgb)"; break; case GE_DSTBLEND_SRCALPHA: dstFactor = "vec3(v.a)"; break; case GE_DSTBLEND_INVSRCALPHA: dstFactor = "vec3(1.0 - v.a)"; break; case GE_DSTBLEND_DSTALPHA: dstFactor = "vec3(destColor.a)"; break; case GE_DSTBLEND_INVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a)"; break; case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "vec3(v.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "vec3(1.0 - v.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "vec3(destColor.a * 2.0)"; break; case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a * 2.0)"; break; case GE_DSTBLEND_FIXB: dstFactor = "u_blendFixB"; break; } switch (eq) { case GE_BLENDMODE_MUL_AND_ADD: WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MUL_AND_SUBTRACT: WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", srcFactor, dstFactor); break; case GE_BLENDMODE_MIN: WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); break; case GE_BLENDMODE_MAX: WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); break; case GE_BLENDMODE_ABSDIFF: WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); break; } } } switch (stencilToAlpha) { case REPLACE_ALPHA_DUALSOURCE: WRITE(p, " fragColor0 = vec4(v.rgb, 0.0);\n"); WRITE(p, " fragColor1 = vec4(0.0, 0.0, 0.0, v.a);\n"); break; case REPLACE_ALPHA_YES: WRITE(p, " %s = vec4(v.rgb, 0.0);\n", fragColor0); break; case REPLACE_ALPHA_NO: WRITE(p, " %s = v;\n", fragColor0); break; } if (stencilToAlpha != REPLACE_ALPHA_NO) { switch (ReplaceAlphaWithStencilType()) { case STENCIL_VALUE_UNIFORM: WRITE(p, " %s.a = u_stencilReplaceValue;\n", fragColor0); break; case STENCIL_VALUE_ZERO: WRITE(p, " %s.a = 0.0;\n", fragColor0); break; case STENCIL_VALUE_ONE: WRITE(p, " %s.a = 1.0;\n", fragColor0); break; case STENCIL_VALUE_UNKNOWN: // Maybe we should even mask away alpha using glColorMask and not change it at all? We do get here // if the stencil mode is KEEP for example. WRITE(p, " %s.a = 0.0;\n", fragColor0); break; case STENCIL_VALUE_KEEP: // Do nothing. We'll mask out the alpha using color mask. break; } } #ifdef DEBUG_SHADER if (doTexture) { WRITE(p, " %s = texture2D(tex, v_texcoord.xy);\n", fragColor0); WRITE(p, " %s += vec4(0.3,0,0.3,0.3);\n", fragColor0); } else { WRITE(p, " %s = vec4(1,0,1,1);\n", fragColor0); } #endif WRITE(p, "}\n"); }
void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) { if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } bool useBlit = gstate_c.Supports(GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT | GPU_SUPPORTS_NV_FRAMEBUFFER_BLIT); bool useNV = useBlit && !gstate_c.Supports(GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT); float srcXFactor = useBlit ? (float)src->renderWidth / (float)src->bufferWidth : 1.0f; float srcYFactor = useBlit ? (float)src->renderHeight / (float)src->bufferHeight : 1.0f; const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2; if (srcBpp != bpp && bpp != 0) { srcXFactor = (srcXFactor * bpp) / srcBpp; } int srcX1 = srcX * srcXFactor; int srcX2 = (srcX + w) * srcXFactor; int srcY1 = srcY * srcYFactor; int srcY2 = (srcY + h) * srcYFactor; float dstXFactor = useBlit ? (float)dst->renderWidth / (float)dst->bufferWidth : 1.0f; float dstYFactor = useBlit ? (float)dst->renderHeight / (float)dst->bufferHeight : 1.0f; const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2; if (dstBpp != bpp && bpp != 0) { dstXFactor = (dstXFactor * bpp) / dstBpp; } int dstX1 = dstX * dstXFactor; int dstX2 = (dstX + w) * dstXFactor; int dstY1 = dstY * dstYFactor; int dstY2 = (dstY + h) * dstYFactor; if (src == dst && srcX == dstX && srcY == dstY) { // Let's just skip a copy where the destination is equal to the source. WARN_LOG_REPORT_ONCE(blitSame, G3D, "Skipped blit with equal dst and src"); return; } if (gstate_c.Supports(GPU_SUPPORTS_ANY_COPY_IMAGE)) { // glBlitFramebuffer can clip, but glCopyImageSubData is more restricted. // In case the src goes outside, we just skip the optimization in that case. const bool sameSize = dstX2 - dstX1 == srcX2 - srcX1 && dstY2 - dstY1 == srcY2 - srcY1; const bool sameDepth = dst->colorDepth == src->colorDepth; const bool srcInsideBounds = srcX2 <= src->renderWidth && srcY2 <= src->renderHeight; const bool dstInsideBounds = dstX2 <= dst->renderWidth && dstY2 <= dst->renderHeight; const bool xOverlap = src == dst && srcX2 > dstX1 && srcX1 < dstX2; const bool yOverlap = src == dst && srcY2 > dstY1 && srcY1 < dstY2; if (sameSize && sameDepth && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { draw_->CopyFramebufferImage(src->fbo, 0, srcX1, srcY1, 0, dst->fbo, 0, dstX1, dstY1, 0, dstX2 - dstX1, dstY2 - dstY1, 1, Draw::FB_COLOR_BIT); return; } } if (useBlit) { draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST); } else { draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); draw_->BindFramebufferAsTexture(src->fbo, 0, Draw::FB_COLOR_BIT, 0); // Make sure our 2D drawing program is ready. Compiles only if not already compiled. CompileDraw2DProgram(); render_->SetViewport({ 0, 0, (float)dst->renderWidth, (float)dst->renderHeight, 0, 1.0f }); render_->SetStencilDisabled(); render_->SetDepth(false, false, GL_ALWAYS); render_->SetNoBlendAndMask(0xF); // The first four coordinates are relative to the 6th and 7th arguments of DrawActiveTexture. // Should maybe revamp that interface. float srcW = src->bufferWidth; float srcH = src->bufferHeight; render_->BindProgram(draw2dprogram_); DrawActiveTexture(dstX1, dstY1, w * dstXFactor, h, dst->bufferWidth, dst->bufferHeight, srcX1 / srcW, srcY1 / srcH, srcX2 / srcW, srcY2 / srcH, ROTATION_LOCKED_HORIZONTAL, DRAWTEX_NEAREST); textureCacheGL_->ForgetLastTexture(); } gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_RASTER_STATE); }