CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/GPU/Common/DrawEngineCommon.cpp
Views: 1401
// Copyright (c) 2013- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <cfloat>1920#include "Common/Data/Convert/ColorConv.h"21#include "Common/Profiler/Profiler.h"22#include "Common/LogReporting.h"23#include "Common/Math/CrossSIMD.h"24#include "Common/Math/lin/matrix4x4.h"25#include "Core/Config.h"26#include "GPU/Common/DrawEngineCommon.h"27#include "GPU/Common/SplineCommon.h"28#include "GPU/Common/VertexDecoderCommon.h"29#include "GPU/ge_constants.h"30#include "GPU/GPUState.h"3132#define QUAD_INDICES_MAX 655363334enum {35TRANSFORMED_VERTEX_BUFFER_SIZE = VERTEX_BUFFER_MAX * sizeof(TransformedVertex)36};3738DrawEngineCommon::DrawEngineCommon() : decoderMap_(16) {39if (g_Config.bVertexDecoderJit && (g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR)) {40decJitCache_ = new VertexDecoderJitCache();41}42transformed_ = (TransformedVertex *)AllocateMemoryPages(TRANSFORMED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);43transformedExpanded_ = (TransformedVertex *)AllocateMemoryPages(3 * TRANSFORMED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);44decoded_ = (u8 *)AllocateMemoryPages(DECODED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);45decIndex_ = (u16 *)AllocateMemoryPages(DECODED_INDEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);46}4748DrawEngineCommon::~DrawEngineCommon() {49FreeMemoryPages(decoded_, DECODED_VERTEX_BUFFER_SIZE);50FreeMemoryPages(decIndex_, DECODED_INDEX_BUFFER_SIZE);51FreeMemoryPages(transformed_, TRANSFORMED_VERTEX_BUFFER_SIZE);52FreeMemoryPages(transformedExpanded_, 3 * TRANSFORMED_VERTEX_BUFFER_SIZE);53delete decJitCache_;54decoderMap_.Iterate([&](const uint32_t vtype, VertexDecoder *decoder) {55delete decoder;56});57ClearSplineBezierWeights();58}5960void DrawEngineCommon::Init() {61NotifyConfigChanged();62}6364VertexDecoder *DrawEngineCommon::GetVertexDecoder(u32 vtype) {65VertexDecoder *dec;66if (decoderMap_.Get(vtype, &dec))67return dec;68dec = new VertexDecoder();69_assert_(dec);70dec->SetVertexType(vtype, decOptions_, decJitCache_);71decoderMap_.Insert(vtype, dec);72return dec;73}7475std::vector<std::string> DrawEngineCommon::DebugGetVertexLoaderIDs() {76std::vector<std::string> ids;77decoderMap_.Iterate([&](const uint32_t vtype, VertexDecoder *decoder) {78std::string id;79id.resize(sizeof(vtype));80memcpy(&id[0], &vtype, sizeof(vtype));81ids.push_back(id);82});83return ids;84}8586std::string DrawEngineCommon::DebugGetVertexLoaderString(std::string id, DebugShaderStringType stringType) {87u32 mapId;88memcpy(&mapId, &id[0], sizeof(mapId));89VertexDecoder *dec;90if (decoderMap_.Get(mapId, &dec)) {91return dec->GetString(stringType);92} else {93return "N/A";94}95}9697static Vec3f ClipToScreen(const Vec4f& coords) {98float xScale = gstate.getViewportXScale();99float xCenter = gstate.getViewportXCenter();100float yScale = gstate.getViewportYScale();101float yCenter = gstate.getViewportYCenter();102float zScale = gstate.getViewportZScale();103float zCenter = gstate.getViewportZCenter();104105float x = coords.x * xScale / coords.w + xCenter;106float y = coords.y * yScale / coords.w + yCenter;107float z = coords.z * zScale / coords.w + zCenter;108109// 16 = 0xFFFF / 4095.9375110return Vec3f(x * 16 - gstate.getOffsetX16(), y * 16 - gstate.getOffsetY16(), z);111}112113static Vec3f ScreenToDrawing(const Vec3f& coords) {114Vec3f ret;115ret.x = coords.x * (1.0f / 16.0f);116ret.y = coords.y * (1.0f / 16.0f);117ret.z = coords.z;118return ret;119}120121void DrawEngineCommon::NotifyConfigChanged() {122if (decJitCache_)123decJitCache_->Clear();124lastVType_ = -1;125dec_ = nullptr;126decoderMap_.Iterate([&](const uint32_t vtype, VertexDecoder *decoder) {127delete decoder;128});129decoderMap_.Clear();130ClearTrackedVertexArrays();131132useHWTransform_ = g_Config.bHardwareTransform;133useHWTessellation_ = UpdateUseHWTessellation(g_Config.bHardwareTessellation);134decOptions_.applySkinInDecode = g_Config.bSoftwareSkinning;135}136137u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, int lowerBound, int upperBound, u32 vertType, int *vertexSize) {138const u32 vertTypeID = GetVertTypeID(vertType, gstate.getUVGenMode(), decOptions_.applySkinInDecode);139VertexDecoder *dec = GetVertexDecoder(vertTypeID);140if (vertexSize)141*vertexSize = dec->VertexSize();142return DrawEngineCommon::NormalizeVertices(outPtr, bufPtr, inPtr, dec, lowerBound, upperBound, vertType);143}144145void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) {146// Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle147// of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into148// through vertices.149// Since the only known use is Thrillville and it only uses it to clear, we just use color and pos.150struct ImmVertex {151float uv[2];152uint32_t color;153float xyz[3];154};155std::vector<ImmVertex> temp;156temp.resize(vertexCount);157uint32_t color1Used = 0;158for (int i = 0; i < vertexCount; i++) {159// Since we're sending through, scale back up to w/h.160temp[i].uv[0] = buffer[i].u * gstate.getTextureWidth(0);161temp[i].uv[1] = buffer[i].v * gstate.getTextureHeight(0);162temp[i].color = buffer[i].color0_32;163temp[i].xyz[0] = buffer[i].pos[0];164temp[i].xyz[1] = buffer[i].pos[1];165temp[i].xyz[2] = buffer[i].pos[2];166color1Used |= buffer[i].color1_32;167}168int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH;169// TODO: Handle fog and secondary color somehow?170171if (gstate.isFogEnabled() && !gstate.isModeThrough()) {172WARN_LOG_REPORT_ONCE(geimmfog, Log::G3D, "Imm vertex used fog");173}174if (color1Used != 0 && gstate.isUsingSecondaryColor() && !gstate.isModeThrough()) {175WARN_LOG_REPORT_ONCE(geimmcolor1, Log::G3D, "Imm vertex used secondary color");176}177178bool prevThrough = gstate.isModeThrough();179// Code checks this reg directly, not just the vtype ID.180if (!prevThrough) {181gstate.vertType |= GE_VTYPE_THROUGH;182gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);183}184185int bytesRead;186uint32_t vertTypeID = GetVertTypeID(vtype, 0, decOptions_.applySkinInDecode);187188bool clockwise = !gstate.isCullEnabled() || gstate.getCullMode() == cullMode;189SubmitPrim(&temp[0], nullptr, prim, vertexCount, vertTypeID, clockwise, &bytesRead);190DispatchFlush();191192if (!prevThrough) {193gstate.vertType &= ~GE_VTYPE_THROUGH;194gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);195}196}197198// Gated by DIRTY_CULL_PLANES199void DrawEngineCommon::UpdatePlanes() {200float view[16];201float viewproj[16];202ConvertMatrix4x3To4x4(view, gstate.viewMatrix);203Matrix4ByMatrix4(viewproj, view, gstate.projMatrix);204205// Next, we need to apply viewport, scissor, region, and even offset - but only for X/Y.206// Note that the PSP does not clip against the viewport.207const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());208// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior.209minOffset_ = baseOffset + Vec2f(std::max(gstate.getRegionRateX() - 0x100, gstate.getScissorX1()), std::max(gstate.getRegionRateY() - 0x100, gstate.getScissorY1())) - Vec2f(1.0f, 1.0f);210maxOffset_ = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2())) + Vec2f(1.0f, 1.0f);211212// Let's not handle these special cases in the fast culler.213offsetOutsideEdge_ = maxOffset_.x >= 4096.0f || minOffset_.x < 1.0f || minOffset_.y < 1.0f || maxOffset_.y >= 4096.0f;214215// Now let's apply the viewport to our scissor/region + offset range.216Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale());217Vec2f minViewport = (minOffset_ - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;218Vec2f maxViewport = (maxOffset_ - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;219220Vec2f viewportInvSize = Vec2f(1.0f / (maxViewport.x - minViewport.x), 1.0f / (maxViewport.y - minViewport.y));221222Lin::Matrix4x4 applyViewport{};223// Scale to the viewport's size.224applyViewport.xx = 2.0f * viewportInvSize.x;225applyViewport.yy = 2.0f * viewportInvSize.y;226applyViewport.zz = 1.0f;227applyViewport.ww = 1.0f;228// And offset to the viewport's centers.229applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x;230applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y;231232float mtx[16];233Matrix4ByMatrix4(mtx, viewproj, applyViewport.m);234// I'm sure there's some fairly optimized way to set these.235planes_.Set(0, mtx[3] - mtx[0], mtx[7] - mtx[4], mtx[11] - mtx[8], mtx[15] - mtx[12]); // Right236planes_.Set(1, mtx[3] + mtx[0], mtx[7] + mtx[4], mtx[11] + mtx[8], mtx[15] + mtx[12]); // Left237planes_.Set(2, mtx[3] + mtx[1], mtx[7] + mtx[5], mtx[11] + mtx[9], mtx[15] + mtx[13]); // Bottom238planes_.Set(3, mtx[3] - mtx[1], mtx[7] - mtx[5], mtx[11] - mtx[9], mtx[15] - mtx[13]); // Top239planes_.Set(4, mtx[3] + mtx[2], mtx[7] + mtx[6], mtx[11] + mtx[10], mtx[15] + mtx[14]); // Near240planes_.Set(5, mtx[3] - mtx[2], mtx[7] - mtx[6], mtx[11] - mtx[10], mtx[15] - mtx[14]); // Far241}242243// This code has plenty of potential for optimization.244//245// It does the simplest and safest test possible: If all points of a bbox is outside a single of246// our clipping planes, we reject the box. Tighter bounds would be desirable but would take more calculations.247// The name is a slight misnomer, because any bounding shape will work, not just boxes.248//249// Potential optimizations:250// * SIMD-ify the plane culling, and also the vertex data conversion (could even group together xxxxyyyyzzzz for example)251// * Compute min/max of the verts, and then compute a bounding sphere and check that against the planes.252// - Less accurate, but..253// - Only requires six plane evaluations then.254255bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int vertexCount, u32 vertType) {256// Grab temp buffer space from large offsets in decoded_. Not exactly safe for large draws.257if (vertexCount > 1024) {258return true;259}260261SimpleVertex *corners = (SimpleVertex *)(decoded_ + 65536 * 12);262float *verts = (float *)(decoded_ + 65536 * 18);263264// Although this may lead to drawing that shouldn't happen, the viewport is more complex on VR.265// Let's always say objects are within bounds.266if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY))267return true;268269// Due to world matrix updates per "thing", this isn't quite as effective as it could be if we did world transform270// in here as well. Though, it still does cut down on a lot of updates in Tekken 6.271if (gstate_c.IsDirty(DIRTY_CULL_PLANES)) {272UpdatePlanes();273gpuStats.numPlaneUpdates++;274gstate_c.Clean(DIRTY_CULL_PLANES);275}276277// Try to skip NormalizeVertices if it's pure positions. No need to bother with a vertex decoder278// and a large vertex format.279if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_FLOAT && !inds) {280memcpy(verts, vdata, sizeof(float) * 3 * vertexCount);281} else if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_8BIT && !inds) {282const s8 *vtx = (const s8 *)vdata;283for (int i = 0; i < vertexCount * 3; i++) {284verts[i] = vtx[i] * (1.0f / 128.0f);285}286} else if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_16BIT && !inds) {287const s16 *vtx = (const s16 *)vdata;288for (int i = 0; i < vertexCount * 3; i++) {289verts[i] = vtx[i] * (1.0f / 32768.0f);290}291} else {292// Simplify away indices, bones, and morph before proceeding.293u8 *temp_buffer = decoded_ + 65536 * 24;294295if ((inds || (vertType & (GE_VTYPE_WEIGHT_MASK | GE_VTYPE_MORPHCOUNT_MASK)))) {296u16 indexLowerBound = 0;297u16 indexUpperBound = (u16)vertexCount - 1;298299if (vertexCount > 0 && inds) {300GetIndexBounds(inds, vertexCount, vertType, &indexLowerBound, &indexUpperBound);301}302// TODO: Avoid normalization if just plain skinning.303// Force software skinning.304bool wasApplyingSkinInDecode = decOptions_.applySkinInDecode;305decOptions_.applySkinInDecode = true;306NormalizeVertices((u8 *)corners, temp_buffer, (const u8 *)vdata, indexLowerBound, indexUpperBound, vertType);307decOptions_.applySkinInDecode = wasApplyingSkinInDecode;308309IndexConverter conv(vertType, inds);310for (int i = 0; i < vertexCount; i++) {311verts[i * 3] = corners[conv(i)].pos.x;312verts[i * 3 + 1] = corners[conv(i)].pos.y;313verts[i * 3 + 2] = corners[conv(i)].pos.z;314}315} else {316// Simple, most common case.317VertexDecoder *dec = GetVertexDecoder(vertType);318int stride = dec->VertexSize();319int offset = dec->posoff;320switch (vertType & GE_VTYPE_POS_MASK) {321case GE_VTYPE_POS_8BIT:322for (int i = 0; i < vertexCount; i++) {323const s8 *data = (const s8 *)vdata + i * stride + offset;324for (int j = 0; j < 3; j++) {325verts[i * 3 + j] = data[j] * (1.0f / 128.0f);326}327}328break;329case GE_VTYPE_POS_16BIT:330for (int i = 0; i < vertexCount; i++) {331const s16 *data = ((const s16 *)((const s8 *)vdata + i * stride + offset));332for (int j = 0; j < 3; j++) {333verts[i * 3 + j] = data[j] * (1.0f / 32768.0f);334}335}336break;337case GE_VTYPE_POS_FLOAT:338for (int i = 0; i < vertexCount; i++)339memcpy(&verts[i * 3], (const u8 *)vdata + stride * i + offset, sizeof(float) * 3);340break;341}342}343}344345// Pretransform the verts in-place so we don't have to do it inside the loop.346// We do this differently in the fast version below since we skip the max/minOffset checks there347// making it easier to get the whole thing ready for SIMD.348for (int i = 0; i < vertexCount; i++) {349float worldpos[3];350Vec3ByMatrix43(worldpos, &verts[i * 3], gstate.worldMatrix);351memcpy(&verts[i * 3], worldpos, 12);352}353354// Note: near/far are not checked without clamp/clip enabled, so we skip those planes.355int totalPlanes = gstate.isDepthClampEnabled() ? 6 : 4;356for (int plane = 0; plane < totalPlanes; plane++) {357int inside = 0;358int out = 0;359for (int i = 0; i < vertexCount; i++) {360// Test against the frustum planes, and count.361// TODO: We should test 4 vertices at a time using SIMD.362// I guess could also test one vertex against 4 planes at a time, though a lot of waste at the common case of 6.363const float *worldpos = verts + i * 3;364float value = planes_.Test(plane, worldpos);365if (value <= -FLT_EPSILON) // Not sure why we use exactly this value. Probably '< 0' would do.366out++;367else368inside++;369}370371// No vertices inside this one plane? Don't need to draw.372if (inside == 0) {373// All out - but check for X and Y if the offset was near the cullbox edge.374bool outsideEdge = false;375switch (plane) {376case 0: outsideEdge = maxOffset_.x >= 4096.0f; break;377case 1: outsideEdge = minOffset_.x < 1.0f; break;378case 2: outsideEdge = minOffset_.y < 1.0f; break;379case 3: outsideEdge = maxOffset_.y >= 4096.0f; break;380}381382// Only consider this outside if offset + scissor/region is fully inside the cullbox.383if (!outsideEdge)384return false;385}386387// Any out. For testing that the planes are in the right locations.388// if (out != 0) return false;389}390return true;391}392393// NOTE: This doesn't handle through-mode, indexing, morph, or skinning.394bool DrawEngineCommon::TestBoundingBoxFast(const void *vdata, int vertexCount, u32 vertType) {395SimpleVertex *corners = (SimpleVertex *)(decoded_ + 65536 * 12);396float *verts = (float *)(decoded_ + 65536 * 18);397398// Although this may lead to drawing that shouldn't happen, the viewport is more complex on VR.399// Let's always say objects are within bounds.400if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY))401return true;402403// Due to world matrix updates per "thing", this isn't quite as effective as it could be if we did world transform404// in here as well. Though, it still does cut down on a lot of updates in Tekken 6.405if (gstate_c.IsDirty(DIRTY_CULL_PLANES)) {406UpdatePlanes();407gpuStats.numPlaneUpdates++;408gstate_c.Clean(DIRTY_CULL_PLANES);409}410411// Also let's just bail if offsetOutsideEdge_ is set, instead of handling the cases.412// NOTE: This is written to in UpdatePlanes so can't check it before.413if (offsetOutsideEdge_)414return true;415416// Simple, most common case.417VertexDecoder *dec = GetVertexDecoder(vertType);418int stride = dec->VertexSize();419int offset = dec->posoff;420int vertStride = 3;421422// TODO: Possibly do the plane tests directly against the source formats instead of converting.423switch (vertType & GE_VTYPE_POS_MASK) {424case GE_VTYPE_POS_8BIT:425for (int i = 0; i < vertexCount; i++) {426const s8 *data = (const s8 *)vdata + i * stride + offset;427for (int j = 0; j < 3; j++) {428verts[i * 3 + j] = data[j] * (1.0f / 128.0f);429}430}431break;432case GE_VTYPE_POS_16BIT:433{434#if PPSSPP_ARCH(SSE2)435__m128 scaleFactor = _mm_set1_ps(1.0f / 32768.0f);436for (int i = 0; i < vertexCount; i++) {437const s16 *data = ((const s16 *)((const s8 *)vdata + i * stride + offset));438__m128i bits = _mm_castpd_si128(_mm_load_sd((const double *)data));439// Sign extension. Hacky without SSE4.440bits = _mm_srai_epi32(_mm_unpacklo_epi16(bits, bits), 16);441__m128 pos = _mm_mul_ps(_mm_cvtepi32_ps(bits), scaleFactor);442_mm_storeu_ps(verts + i * 3, pos); // TODO: use stride 4 to avoid clashing writes?443}444#elif PPSSPP_ARCH(ARM_NEON)445for (int i = 0; i < vertexCount; i++) {446const s16 *dataPtr = ((const s16 *)((const s8 *)vdata + i * stride + offset));447int32x4_t data = vmovl_s16(vld1_s16(dataPtr));448float32x4_t pos = vcvtq_n_f32_s32(data, 15); // >> 15 = division by 32768.0f449vst1q_f32(verts + i * 3, pos);450}451#else452for (int i = 0; i < vertexCount; i++) {453const s16 *data = ((const s16 *)((const s8 *)vdata + i * stride + offset));454for (int j = 0; j < 3; j++) {455verts[i * 3 + j] = data[j] * (1.0f / 32768.0f);456}457}458#endif459break;460}461case GE_VTYPE_POS_FLOAT:462// No need to copy in this case, we can just read directly from the source format with a stride.463verts = (float *)((uint8_t *)vdata + offset);464vertStride = stride / 4;465break;466}467468// We only check the 4 sides. Near/far won't likely make a huge difference.469// We test one vertex against 4 planes to get some SIMD. Vertices need to be transformed to world space470// for testing, don't want to re-do that, so we have to use that "pivot" of the data.471#if PPSSPP_ARCH(SSE2)472const __m128 worldX = _mm_loadu_ps(gstate.worldMatrix);473const __m128 worldY = _mm_loadu_ps(gstate.worldMatrix + 3);474const __m128 worldZ = _mm_loadu_ps(gstate.worldMatrix + 6);475const __m128 worldW = _mm_loadu_ps(gstate.worldMatrix + 9);476const __m128 planeX = _mm_loadu_ps(planes_.x);477const __m128 planeY = _mm_loadu_ps(planes_.y);478const __m128 planeZ = _mm_loadu_ps(planes_.z);479const __m128 planeW = _mm_loadu_ps(planes_.w);480__m128 inside = _mm_set1_ps(0.0f);481for (int i = 0; i < vertexCount; i++) {482const float *pos = verts + i * vertStride;483__m128 worldpos = _mm_add_ps(484_mm_add_ps(485_mm_mul_ps(worldX, _mm_set1_ps(pos[0])),486_mm_mul_ps(worldY, _mm_set1_ps(pos[1]))487),488_mm_add_ps(489_mm_mul_ps(worldZ, _mm_set1_ps(pos[2])),490worldW491)492);493// OK, now we check it against the four planes.494// This is really curiously similar to a matrix multiplication (well, it is one).495__m128 posX = _mm_shuffle_ps(worldpos, worldpos, _MM_SHUFFLE(0, 0, 0, 0));496__m128 posY = _mm_shuffle_ps(worldpos, worldpos, _MM_SHUFFLE(1, 1, 1, 1));497__m128 posZ = _mm_shuffle_ps(worldpos, worldpos, _MM_SHUFFLE(2, 2, 2, 2));498__m128 planeDist = _mm_add_ps(499_mm_add_ps(500_mm_mul_ps(planeX, posX),501_mm_mul_ps(planeY, posY)502),503_mm_add_ps(504_mm_mul_ps(planeZ, posZ),505planeW506)507);508inside = _mm_or_ps(inside, _mm_cmpge_ps(planeDist, _mm_setzero_ps()));509}510// 0xF means that we found at least one vertex inside every one of the planes.511// We don't bother with counts, though it wouldn't be hard if we had a use for them.512return _mm_movemask_ps(inside) == 0xF;513#elif PPSSPP_ARCH(ARM_NEON)514const float32x4_t worldX = vld1q_f32(gstate.worldMatrix);515const float32x4_t worldY = vld1q_f32(gstate.worldMatrix + 3);516const float32x4_t worldZ = vld1q_f32(gstate.worldMatrix + 6);517const float32x4_t worldW = vld1q_f32(gstate.worldMatrix + 9);518const float32x4_t planeX = vld1q_f32(planes_.x);519const float32x4_t planeY = vld1q_f32(planes_.y);520const float32x4_t planeZ = vld1q_f32(planes_.z);521const float32x4_t planeW = vld1q_f32(planes_.w);522uint32x4_t inside = vdupq_n_u32(0);523for (int i = 0; i < vertexCount; i++) {524const float *pos = verts + i * vertStride;525float32x4_t objpos = vld1q_f32(pos);526float32x4_t worldpos = vaddq_f32(527vmlaq_laneq_f32(528vmulq_laneq_f32(worldX, objpos, 0),529worldY, objpos, 1),530vmlaq_laneq_f32(worldW, worldZ, objpos, 2)531);532// OK, now we check it against the four planes.533// This is really curiously similar to a matrix multiplication (well, it is one).534float32x4_t planeDist = vaddq_f32(535vmlaq_laneq_f32(536vmulq_laneq_f32(planeX, worldpos, 0),537planeY, worldpos, 1),538vmlaq_laneq_f32(planeW, planeZ, worldpos, 2)539);540inside = vorrq_u32(inside, vcgezq_f32(planeDist));541}542uint64_t insideBits = vget_lane_u64(vreinterpret_u64_u16(vmovn_u32(inside)), 0);543return ~insideBits == 0; // InsideBits all ones means that we found at least one vertex inside every one of the planes. We don't bother with counts, though it wouldn't be hard.544#else545int inside[4]{};546for (int i = 0; i < vertexCount; i++) {547const float *pos = verts + i * vertStride;548float worldpos[3];549Vec3ByMatrix43(worldpos, pos, gstate.worldMatrix);550for (int plane = 0; plane < 4; plane++) {551float value = planes_.Test(plane, worldpos);552if (value >= 0.0f)553inside[plane]++;554}555}556557for (int plane = 0; plane < 4; plane++) {558if (inside[plane] == 0) {559return false;560}561}562#endif563return true;564}565566// TODO: This probably is not the best interface.567bool DrawEngineCommon::GetCurrentSimpleVertices(int count, std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices) {568// This is always for the current vertices.569u16 indexLowerBound = 0;570u16 indexUpperBound = count - 1;571572if (!Memory::IsValidAddress(gstate_c.vertexAddr) || count == 0)573return false;574575bool savedVertexFullAlpha = gstate_c.vertexFullAlpha;576577if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {578const u8 *inds = Memory::GetPointer(gstate_c.indexAddr);579const u16_le *inds16 = (const u16_le *)inds;580const u32_le *inds32 = (const u32_le *)inds;581582if (inds) {583GetIndexBounds(inds, count, gstate.vertType, &indexLowerBound, &indexUpperBound);584indices.resize(count);585switch (gstate.vertType & GE_VTYPE_IDX_MASK) {586case GE_VTYPE_IDX_8BIT:587for (int i = 0; i < count; ++i) {588indices[i] = inds[i];589}590break;591case GE_VTYPE_IDX_16BIT:592for (int i = 0; i < count; ++i) {593indices[i] = inds16[i];594}595break;596case GE_VTYPE_IDX_32BIT:597WARN_LOG_REPORT_ONCE(simpleIndexes32, Log::G3D, "SimpleVertices: Decoding 32-bit indexes");598for (int i = 0; i < count; ++i) {599// These aren't documented and should be rare. Let's bounds check each one.600if (inds32[i] != (u16)inds32[i]) {601ERROR_LOG_REPORT_ONCE(simpleIndexes32Bounds, Log::G3D, "SimpleVertices: Index outside 16-bit range");602}603indices[i] = (u16)inds32[i];604}605break;606}607} else {608indices.clear();609}610} else {611indices.clear();612}613614static std::vector<u32> temp_buffer;615static std::vector<SimpleVertex> simpleVertices;616temp_buffer.resize(std::max((int)indexUpperBound, 8192) * 128 / sizeof(u32));617simpleVertices.resize(indexUpperBound + 1);618NormalizeVertices((u8 *)(&simpleVertices[0]), (u8 *)(&temp_buffer[0]), Memory::GetPointerUnchecked(gstate_c.vertexAddr), indexLowerBound, indexUpperBound, gstate.vertType);619620float world[16];621float view[16];622float worldview[16];623float worldviewproj[16];624ConvertMatrix4x3To4x4(world, gstate.worldMatrix);625ConvertMatrix4x3To4x4(view, gstate.viewMatrix);626Matrix4ByMatrix4(worldview, world, view);627Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix);628629vertices.resize(indexUpperBound + 1);630uint32_t vertType = gstate.vertType;631for (int i = indexLowerBound; i <= indexUpperBound; ++i) {632const SimpleVertex &vert = simpleVertices[i];633634if ((vertType & GE_VTYPE_THROUGH) != 0) {635if (vertType & GE_VTYPE_TC_MASK) {636vertices[i].u = vert.uv[0];637vertices[i].v = vert.uv[1];638} else {639vertices[i].u = 0.0f;640vertices[i].v = 0.0f;641}642vertices[i].x = vert.pos.x;643vertices[i].y = vert.pos.y;644vertices[i].z = vert.pos.z;645if (vertType & GE_VTYPE_COL_MASK) {646memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c));647} else {648memset(vertices[i].c, 0, sizeof(vertices[i].c));649}650vertices[i].nx = 0; // No meaningful normals in through mode651vertices[i].ny = 0;652vertices[i].nz = 1.0f;653} else {654float clipPos[4];655Vec3ByMatrix44(clipPos, vert.pos.AsArray(), worldviewproj);656Vec3f screenPos = ClipToScreen(clipPos);657Vec3f drawPos = ScreenToDrawing(screenPos);658659if (vertType & GE_VTYPE_TC_MASK) {660vertices[i].u = vert.uv[0] * (float)gstate.getTextureWidth(0);661vertices[i].v = vert.uv[1] * (float)gstate.getTextureHeight(0);662} else {663vertices[i].u = 0.0f;664vertices[i].v = 0.0f;665}666// Should really have separate coordinates for before and after transform.667vertices[i].x = drawPos.x;668vertices[i].y = drawPos.y;669vertices[i].z = drawPos.z;670if (vertType & GE_VTYPE_COL_MASK) {671memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c));672} else {673memset(vertices[i].c, 0, sizeof(vertices[i].c));674}675vertices[i].nx = vert.nrm.x;676vertices[i].ny = vert.nrm.y;677vertices[i].nz = vert.nrm.z;678}679}680681gstate_c.vertexFullAlpha = savedVertexFullAlpha;682683return true;684}685686// This normalizes a set of vertices in any format to SimpleVertex format, by processing away morphing AND skinning.687// The rest of the transform pipeline like lighting will go as normal, either hardware or software.688// The implementation is initially a bit inefficient but shouldn't be a big deal.689// An intermediate buffer of not-easy-to-predict size is stored at bufPtr.690u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, VertexDecoder *dec, int lowerBound, int upperBound, u32 vertType) {691// First, decode the vertices into a GPU compatible format. This step can be eliminated but will need a separate692// implementation of the vertex decoder.693dec->DecodeVerts(bufPtr, inPtr, &gstate_c.uv, lowerBound, upperBound);694695// OK, morphing eliminated but bones still remain to be taken care of.696// Let's do a partial software transform where we only do skinning.697698VertexReader reader(bufPtr, dec->GetDecVtxFmt(), vertType);699700SimpleVertex *sverts = (SimpleVertex *)outPtr;701702const u8 defaultColor[4] = {703(u8)gstate.getMaterialAmbientR(),704(u8)gstate.getMaterialAmbientG(),705(u8)gstate.getMaterialAmbientB(),706(u8)gstate.getMaterialAmbientA(),707};708709// Let's have two separate loops, one for non skinning and one for skinning.710if (!dec->skinInDecode && (vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) {711int numBoneWeights = vertTypeGetNumBoneWeights(vertType);712for (int i = lowerBound; i <= upperBound; i++) {713reader.Goto(i - lowerBound);714SimpleVertex &sv = sverts[i];715if (vertType & GE_VTYPE_TC_MASK) {716reader.ReadUV(sv.uv);717}718719if (vertType & GE_VTYPE_COL_MASK) {720sv.color_32 = reader.ReadColor0_8888();721} else {722memcpy(sv.color, defaultColor, 4);723}724725float nrm[3], pos[3];726float bnrm[3], bpos[3];727728if (vertType & GE_VTYPE_NRM_MASK) {729// Normals are generated during tessellation anyway, not sure if any need to supply730reader.ReadNrm(nrm);731} else {732nrm[0] = 0;733nrm[1] = 0;734nrm[2] = 1.0f;735}736reader.ReadPos(pos);737738// Apply skinning transform directly739float weights[8];740reader.ReadWeights(weights);741// Skinning742Vec3Packedf psum(0, 0, 0);743Vec3Packedf nsum(0, 0, 0);744for (int w = 0; w < numBoneWeights; w++) {745if (weights[w] != 0.0f) {746Vec3ByMatrix43(bpos, pos, gstate.boneMatrix + w * 12);747Vec3Packedf tpos(bpos);748psum += tpos * weights[w];749750Norm3ByMatrix43(bnrm, nrm, gstate.boneMatrix + w * 12);751Vec3Packedf tnorm(bnrm);752nsum += tnorm * weights[w];753}754}755sv.pos = psum;756sv.nrm = nsum;757}758} else {759for (int i = lowerBound; i <= upperBound; i++) {760reader.Goto(i - lowerBound);761SimpleVertex &sv = sverts[i];762if (vertType & GE_VTYPE_TC_MASK) {763reader.ReadUV(sv.uv);764} else {765sv.uv[0] = 0.0f; // This will get filled in during tessellation766sv.uv[1] = 0.0f;767}768if (vertType & GE_VTYPE_COL_MASK) {769sv.color_32 = reader.ReadColor0_8888();770} else {771memcpy(sv.color, defaultColor, 4);772}773if (vertType & GE_VTYPE_NRM_MASK) {774// Normals are generated during tessellation anyway, not sure if any need to supply775reader.ReadNrm((float *)&sv.nrm);776} else {777sv.nrm.x = 0.0f;778sv.nrm.y = 0.0f;779sv.nrm.z = 1.0f;780}781reader.ReadPos((float *)&sv.pos);782}783}784785// Okay, there we are! Return the new type (but keep the index bits)786return GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_NRM_FLOAT | GE_VTYPE_POS_FLOAT | (vertType & (GE_VTYPE_IDX_MASK | GE_VTYPE_THROUGH));787}788789void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) {790if (gstate_c.Use(GPU_USE_FRAMEBUFFER_FETCH)) {791*fboTexState = FBO_TEX_READ_FRAMEBUFFER;792} else {793gpuStats.numCopiesForShaderBlend++;794*fboTexState = FBO_TEX_COPY_BIND_TEX;795}796797gstate_c.Dirty(DIRTY_SHADERBLEND);798}799800int DrawEngineCommon::ComputeNumVertsToDecode() const {801int sum = 0;802for (int i = 0; i < numDrawVerts_; i++) {803sum += drawVerts_[i].indexUpperBound + 1 - drawVerts_[i].indexLowerBound;804}805return sum;806}807808int DrawEngineCommon::ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle) {809const uint32_t *start = cmd;810int prevDrawVerts = numDrawVerts_ - 1;811DeferredVerts &dv = drawVerts_[prevDrawVerts];812int offset = dv.vertexCount;813814_dbg_assert_(numDrawInds_ <= MAX_DEFERRED_DRAW_INDS); // if it's equal, the check below will take care of it before any action is taken.815_dbg_assert_(numDrawVerts_ > 0);816817if (!clockwise) {818anyCCWOrIndexed_ = true;819}820int seenPrims = 0;821while (cmd != stall) {822uint32_t data = *cmd;823if ((data & 0xFFF80000) != 0x04000000) {824break;825}826GEPrimitiveType newPrim = static_cast<GEPrimitiveType>((data >> 16) & 7);827if (IsTrianglePrim(newPrim) != isTriangle)828break;829int vertexCount = data & 0xFFFF;830if (numDrawInds_ >= MAX_DEFERRED_DRAW_INDS || vertexCountInDrawCalls_ + offset + vertexCount > VERTEX_BUFFER_MAX) {831break;832}833DeferredInds &di = drawInds_[numDrawInds_++];834di.indexType = 0;835di.prim = newPrim;836seenPrims |= (1 << newPrim);837di.clockwise = clockwise;838di.vertexCount = vertexCount;839di.vertDecodeIndex = prevDrawVerts;840di.offset = offset;841offset += vertexCount;842cmd++;843}844845seenPrims_ |= seenPrims;846847int totalCount = offset - dv.vertexCount;848dv.vertexCount = offset;849dv.indexUpperBound = dv.vertexCount - 1;850vertexCountInDrawCalls_ += totalCount;851*bytesRead = totalCount * dec_->VertexSize();852return cmd - start;853}854855void DrawEngineCommon::SkipPrim(GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int *bytesRead) {856if (!indexGen.PrimCompatible(prevPrim_, prim)) {857DispatchFlush();858}859860// This isn't exactly right, if we flushed, since prims can straddle previous calls.861// But it generally works for common usage.862if (prim == GE_PRIM_KEEP_PREVIOUS) {863// Has to be set to something, let's assume POINTS (0) if no previous.864if (prevPrim_ == GE_PRIM_INVALID)865prevPrim_ = GE_PRIM_POINTS;866prim = prevPrim_;867} else {868prevPrim_ = prim;869}870871// If vtype has changed, setup the vertex decoder.872if (vertTypeID != lastVType_ || !dec_) {873dec_ = GetVertexDecoder(vertTypeID);874lastVType_ = vertTypeID;875}876877*bytesRead = vertexCount * dec_->VertexSize();878}879880// vertTypeID is the vertex type but with the UVGen mode smashed into the top bits.881bool DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead) {882if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawVerts_ >= MAX_DEFERRED_DRAW_VERTS || numDrawInds_ >= MAX_DEFERRED_DRAW_INDS || vertexCountInDrawCalls_ + vertexCount > VERTEX_BUFFER_MAX) {883DispatchFlush();884}885_dbg_assert_(numDrawVerts_ < MAX_DEFERRED_DRAW_VERTS);886_dbg_assert_(numDrawInds_ < MAX_DEFERRED_DRAW_INDS);887888// This isn't exactly right, if we flushed, since prims can straddle previous calls.889// But it generally works for common usage.890if (prim == GE_PRIM_KEEP_PREVIOUS) {891// Has to be set to something, let's assume POINTS (0) if no previous.892if (prevPrim_ == GE_PRIM_INVALID)893prevPrim_ = GE_PRIM_POINTS;894prim = prevPrim_;895} else {896prevPrim_ = prim;897}898899// If vtype has changed, setup the vertex decoder. Don't need to nullcheck dec_ since we set lastVType_ to an invalid value whenever we null it.900if (vertTypeID != lastVType_) {901dec_ = GetVertexDecoder(vertTypeID);902lastVType_ = vertTypeID;903}904905*bytesRead = vertexCount * dec_->VertexSize();906907// Check that we have enough vertices to form the requested primitive.908if (vertexCount < 3) {909if ((vertexCount < 2 && prim > 0) || (prim > GE_PRIM_LINE_STRIP && prim != GE_PRIM_RECTANGLES)) {910return false;911}912if (vertexCount <= 0) {913// Unfortunately we need to do this check somewhere since GetIndexBounds doesn't handle zero-length arrays.914return false;915}916}917918bool applySkin = (vertTypeID & GE_VTYPE_WEIGHT_MASK) && decOptions_.applySkinInDecode;919920DeferredInds &di = drawInds_[numDrawInds_++];921di.inds = inds;922int indexType = (vertTypeID & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT;923if (indexType) {924anyCCWOrIndexed_ = true;925}926di.indexType = indexType;927di.prim = prim;928di.clockwise = clockwise;929if (!clockwise) {930anyCCWOrIndexed_ = true;931}932di.vertexCount = vertexCount;933di.vertDecodeIndex = numDrawVerts_;934di.offset = 0;935936_dbg_assert_(numDrawVerts_ <= MAX_DEFERRED_DRAW_VERTS);937_dbg_assert_(numDrawInds_ <= MAX_DEFERRED_DRAW_INDS);938939if (inds && numDrawVerts_ > decodeVertsCounter_ && drawVerts_[numDrawVerts_ - 1].verts == verts && !applySkin) {940// Same vertex pointer as a previous un-decoded draw call - let's just extend the decode!941di.vertDecodeIndex = numDrawVerts_ - 1;942u16 lb;943u16 ub;944GetIndexBounds(inds, vertexCount, vertTypeID, &lb, &ub);945DeferredVerts &dv = drawVerts_[numDrawVerts_ - 1];946if (lb < dv.indexLowerBound)947dv.indexLowerBound = lb;948if (ub > dv.indexUpperBound)949dv.indexUpperBound = ub;950} else {951// Record a new draw, and a new index gen.952DeferredVerts &dv = drawVerts_[numDrawVerts_++];953dv.verts = verts;954dv.vertexCount = vertexCount;955dv.uvScale = gstate_c.uv;956// Does handle the unindexed case.957GetIndexBounds(inds, vertexCount, vertTypeID, &dv.indexLowerBound, &dv.indexUpperBound);958}959960vertexCountInDrawCalls_ += vertexCount;961seenPrims_ |= (1 << prim);962963if (prim == GE_PRIM_RECTANGLES && (gstate.getTextureAddress(0) & 0x3FFFFFFF) == (gstate.getFrameBufAddress() & 0x3FFFFFFF)) {964// This prevents issues with consecutive self-renders in Ridge Racer.965gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);966DispatchFlush();967}968return true;969}970971void DrawEngineCommon::DecodeVerts(u8 *dest) {972// Note that this should be able to continue a partial decode - we don't necessarily start from zero here (although we do most of the time).973int i = decodeVertsCounter_;974int stride = (int)dec_->GetDecVtxFmt().stride;975for (; i < numDrawVerts_; i++) {976DeferredVerts &dv = drawVerts_[i];977978int indexLowerBound = dv.indexLowerBound;979drawVertexOffsets_[i] = numDecodedVerts_ - indexLowerBound;980981int indexUpperBound = dv.indexUpperBound;982983if (indexUpperBound + 1 - indexLowerBound + numDecodedVerts_ >= VERTEX_BUFFER_MAX) {984// Hit our limit! Stop decoding in this draw.985break;986}987988// Decode the verts (and at the same time apply morphing/skinning). Simple.989dec_->DecodeVerts(dest + numDecodedVerts_ * stride, dv.verts, &dv.uvScale, indexLowerBound, indexUpperBound);990numDecodedVerts_ += indexUpperBound - indexLowerBound + 1;991}992decodeVertsCounter_ = i;993}994995int DrawEngineCommon::DecodeInds() {996// Note that this should be able to continue a partial decode - we don't necessarily start from zero here (although we do most of the time).997998int i = decodeIndsCounter_;999for (; i < numDrawInds_; i++) {1000const DeferredInds &di = drawInds_[i];10011002int indexOffset = drawVertexOffsets_[di.vertDecodeIndex] + di.offset;1003bool clockwise = di.clockwise;1004// We've already collapsed subsequent draws with the same vertex pointer, so no tricky logic here anymore.1005// 2. Loop through the drawcalls, translating indices as we go.1006switch (di.indexType) {1007case GE_VTYPE_IDX_NONE >> GE_VTYPE_IDX_SHIFT:1008indexGen.AddPrim(di.prim, di.vertexCount, indexOffset, clockwise);1009break;1010case GE_VTYPE_IDX_8BIT >> GE_VTYPE_IDX_SHIFT:1011indexGen.TranslatePrim(di.prim, di.vertexCount, (const u8 *)di.inds, indexOffset, clockwise);1012break;1013case GE_VTYPE_IDX_16BIT >> GE_VTYPE_IDX_SHIFT:1014indexGen.TranslatePrim(di.prim, di.vertexCount, (const u16_le *)di.inds, indexOffset, clockwise);1015break;1016case GE_VTYPE_IDX_32BIT >> GE_VTYPE_IDX_SHIFT:1017indexGen.TranslatePrim(di.prim, di.vertexCount, (const u32_le *)di.inds, indexOffset, clockwise);1018break;1019}1020}1021decodeIndsCounter_ = i;10221023return indexGen.VertexCount();1024}10251026bool DrawEngineCommon::CanUseHardwareTransform(int prim) const {1027if (!useHWTransform_)1028return false;1029return !gstate.isModeThrough() && prim != GE_PRIM_RECTANGLES && prim > GE_PRIM_LINE_STRIP;1030}10311032bool DrawEngineCommon::CanUseHardwareTessellation(GEPatchPrimType prim) const {1033if (useHWTessellation_) {1034return CanUseHardwareTransform(PatchPrimToPrim(prim));1035}1036return false;1037}10381039void TessellationDataTransfer::CopyControlPoints(float *pos, float *tex, float *col, int posStride, int texStride, int colStride, const SimpleVertex *const *points, int size, u32 vertType) {1040bool hasColor = (vertType & GE_VTYPE_COL_MASK) != 0;1041bool hasTexCoord = (vertType & GE_VTYPE_TC_MASK) != 0;10421043for (int i = 0; i < size; ++i) {1044memcpy(pos, points[i]->pos.AsArray(), 3 * sizeof(float));1045pos += posStride;1046}1047if (hasTexCoord) {1048for (int i = 0; i < size; ++i) {1049memcpy(tex, points[i]->uv, 2 * sizeof(float));1050tex += texStride;1051}1052}1053if (hasColor) {1054for (int i = 0; i < size; ++i) {1055memcpy(col, Vec4f::FromRGBA(points[i]->color_32).AsArray(), 4 * sizeof(float));1056col += colStride;1057}1058}1059}10601061bool DrawEngineCommon::DescribeCodePtr(const u8 *ptr, std::string &name) const {1062if (!decJitCache_ || !decJitCache_->IsInSpace(ptr)) {1063return false;1064}10651066// Loop through all the decoders and see if we have a match.1067VertexDecoder *found = nullptr;1068u32 foundKey;10691070decoderMap_.Iterate([&](u32 key, VertexDecoder *value) {1071if (!found) {1072if (value->IsInSpace(ptr)) {1073foundKey = key;1074found = value;1075}1076}1077});10781079if (found) {1080char temp[256];1081found->ToString(temp, false);1082name = temp;1083snprintf(temp, sizeof(temp), "_%08X", foundKey);1084name += temp;1085return true;1086} else {1087return false;1088}1089}109010911092