#include "stdafx.h" #include "LightGrid.h" #include "FastLog.h" #include "rbx/Debug.h" #include "v8world/ContactManagerSpatialHash.h" #include "v8datamodel/PartInstance.h" #include "v8datamodel/Light.h" #include "v8datamodel/MegaCluster.h" #include "v8datamodel/BlockMesh.h" #include "v8datamodel/CylinderMesh.h" #include "v8datamodel/SpecialMesh.h" #include "GfxCore/Device.h" #include "CullableSceneNode.h" #include "LightObject.h" #include "VisualEngine.h" #include "Util.h" #include "SpatialHashedScene.h" #include "rbx/DenseHash.h" #include "rbx/Profiler.h" #if defined(__APPLE__) #include "TargetConditionals.h" #endif #if (defined(RBX_PLATFORM_IOS) && !TARGET_IPHONE_SIMULATOR) || defined(__ANDROID__) #include #endif LOGVARIABLE(RenderLightGrid, 0) LOGVARIABLE(RenderLightGridAgeProportion, 5) LOGVARIABLE(RenderLightGridBorderGlobalCutoff, 32) LOGVARIABLE(RenderLightGridBorderSkylightCutoff, 32) #if defined(_WIN32) || (defined(__APPLE__) && !defined(RBX_PLATFORM_IOS)) #define SIMD_SSE2 #endif #if (defined(RBX_PLATFORM_IOS) && !TARGET_IPHONE_SIMULATOR) || defined(__ANDROID__) #define SIMD_NEON #endif #if defined(SIMD_SSE2) #define SIMDCALL(func, args) (useSIMD ? func##SIMD args : func args) #define SIMDCALLSSE(func, args) (useSIMD ? func##SIMD args : func args) #elif defined(SIMD_NEON) #define SIMDCALL(func, args) (useSIMD ? func##SIMD args : func args) #define SIMDCALLSSE(func, args) func args #else #define SIMDCALL(func, args) func args #define SIMDCALLSSE(func, args) func args #endif namespace RBX { namespace Graphics { const int kLightGridDirectionalDirtyThreshold = 4; const int kLightGridSkylightDirtyThreshold = 4; const int kLightGridShadowDirtyThreshold = 4; // branchless equivalent of std::max(0, v) inline unsigned char max0(int v) { return v & ~(v >> 31); } // branchless equivalent of std::min(255, v) inline unsigned char min255(int v) { return v | ((255 - v) >> 31); } // keep track of dirty state by or-ing together abs(difference) inline void updateWithDirtyFlag(unsigned char& target, int value, unsigned int& dirty) { int diff = target - value; int diffsign = diff >> 31; target = value; dirty |= (diff ^ diffsign) - diffsign; } inline Vector3int32 getGridPosition(const Vector3& v) { float gx = v.x / 4.f; float gy = v.y / 4.f; float gz = v.z / 4.f; return Vector3int32(fastFloorInt(gx), fastFloorInt(gy), fastFloorInt(gz)); } inline Vector3int32 getChunkIndexFromGridPosition(const Vector3int32& pos) { BOOST_STATIC_ASSERT(kLightGridChunkSizeXZ == 1 << boost::static_log2::value); BOOST_STATIC_ASSERT(kLightGridChunkSizeY == 1 << boost::static_log2::value); // Use right shift instead of division since right shift rounds down instead of truncating int cx = pos.x >> boost::static_log2::value; int cy = pos.y >> boost::static_log2::value; int cz = pos.z >> boost::static_log2::value; return Vector3int32(cx, cy, cz); } inline unsigned int getArrayOffset(const Vector3int32& index, const Vector3int32& size) { return index.x + (index.z + index.y * size.z) * size.x; } Vector3int32 convertToFixedPointNormalized(float x, float y, float z, int bits) { int targetSum = 1 << bits; // Convert value to fixed point int resultX = G3D::clamp(x, 0.f, 1.f) * targetSum + 0.5f; int resultY = G3D::clamp(y, 0.f, 1.f) * targetSum + 0.5f; int resultZ = G3D::clamp(z, 0.f, 1.f) * targetSum + 0.5f; // Normalize sum // If the sum was approximately targetSum, this distributes the error roughly proportionately // If the sum was very different (i.e. much larger?), then we may get bogus results. // Order is significant here - if the error is not evenly divisible by 3, we'll adjust Y more. resultZ = std::max(0, resultZ + (targetSum - (resultX + resultY + resultZ)) / 3); resultX = std::max(0, resultX + (targetSum - (resultX + resultY + resultZ)) / 2); resultY = std::max(0, resultY + (targetSum - (resultX + resultY + resultZ)) / 1); return Vector3int32(resultX, resultY, resultZ); } Vector3 getLightContributionZ1(float x, float y) { float wedgeVolumeX = x / 2; float wedgeVolumeY = y / 2; float cornerVolume = x * y / 6; float contribX = wedgeVolumeX - cornerVolume; float contribY = wedgeVolumeY - cornerVolume; return Vector3(contribX, contribY, 1 - contribX - contribY); } Vector3int32 getLightContribution(const Vector3& direction, int bits) { float x = fabsf(direction.x); float y = fabsf(direction.y); float z = fabsf(direction.z); if (z >= y && z >= x) { Vector3 result = getLightContributionZ1(x / z, y / z); return convertToFixedPointNormalized(result.x, result.y, result.z, bits); } else if (y >= z && y >= x) { Vector3 result = getLightContributionZ1(x / y, z / y); return convertToFixedPointNormalized(result.x, result.z, result.y, bits); } else { Vector3 result = getLightContributionZ1(y / x, z / x); return convertToFixedPointNormalized(result.z, result.x, result.y, bits); } } Vector3int32 getLightContributionApprox(const Vector3& direction, int bits) { return convertToFixedPointNormalized(direction.x * direction.x, direction.y * direction.y, direction.z * direction.z, bits); } void fillDummyLighting(LightGridChunk& chunk, unsigned char global, unsigned char skylight) { unsigned char rowData[kLightGridChunkSizeXZ][4]; for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { rowData[x][0] = 0; rowData[x][1] = 0; rowData[x][2] = 0; rowData[x][3] = global; } for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) memcpy(chunk.lighting[y][z][0], rowData[0], sizeof(rowData)); memset(chunk.lightingSkylight, skylight, sizeof(chunk.lightingSkylight)); int averageWeight = kLightGridChunkSizeXZ * kLightGridChunkSizeXZ * 255; for (int y = 0; y < kLightGridChunkSizeY; ++y) { chunk.lightingAverageGlobal[y] = global * averageWeight; chunk.lightingAverageSkylight[y] = skylight * averageWeight; chunk.lightingAverageWeight[y] = averageWeight; } memset(chunk.irradianceFringeX.data, 255, sizeof(chunk.irradianceFringeX.data)); memset(chunk.irradianceFringeY.data, 255, sizeof(chunk.irradianceFringeY.data)); memset(chunk.irradianceFringeZ.data, 255, sizeof(chunk.irradianceFringeZ.data)); memset(chunk.irradianceFringeSkyY.data, 255, sizeof(chunk.irradianceFringeSkyY.data)); memset(chunk.irradianceFringeSkyX0.data, 255, sizeof(chunk.irradianceFringeSkyX0.data)); memset(chunk.irradianceFringeSkyX1.data, 255, sizeof(chunk.irradianceFringeSkyX1.data)); memset(chunk.irradianceFringeSkyZ0.data, 255, sizeof(chunk.irradianceFringeSkyZ0.data)); memset(chunk.irradianceFringeSkyZ1.data, 255, sizeof(chunk.irradianceFringeSkyZ1.data)); } typedef unsigned char LightingData[kLightGridChunkSizeY][kLightGridChunkSizeXZ][kLightGridChunkSizeXZ][4]; void fillLightingBorderX(LightingData& lighting, int x, const Color3uint8& skyAmbient) { for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { lighting[y][z][x][0] = skyAmbient.r; lighting[y][z][x][1] = skyAmbient.g; lighting[y][z][x][2] = skyAmbient.b; lighting[y][z][x][3] = 255; } } void fillLightingBorderY(LightingData& lighting, int y, const Color3uint8& skyAmbient) { unsigned char rowData[kLightGridChunkSizeXZ][4]; for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { rowData[x][0] = skyAmbient.r; rowData[x][1] = skyAmbient.g; rowData[x][2] = skyAmbient.b; rowData[x][3] = 255; } for (int z = 0; z < kLightGridChunkSizeXZ; ++z) memcpy(lighting[y][z][0], rowData[0], sizeof(rowData)); } void fillLightingBorderZ(LightingData& lighting, int z, const Color3uint8& skyAmbient) { unsigned char rowData[kLightGridChunkSizeXZ][4]; for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { rowData[x][0] = skyAmbient.r; rowData[x][1] = skyAmbient.g; rowData[x][2] = skyAmbient.b; rowData[x][3] = 255; } for (int y = 0; y < kLightGridChunkSizeY; ++y) memcpy(lighting[y][z][0], rowData[0], sizeof(rowData)); } void fillLightingBorders(LightingData& lighting, const Vector3int32& index, const Vector3int32& corner, const Vector3int32& size, const Color3uint8& skyAmbient) { if (index.x == corner.x) fillLightingBorderX(lighting, 0, skyAmbient); if (index.x == corner.x + size.x - 1) fillLightingBorderX(lighting, kLightGridChunkSizeXZ - 1, skyAmbient); if (index.y == corner.y) { // We fill two bottom rows to make sure that the lookup-table-based filtering samples both values as a border color fillLightingBorderY(lighting, 0, skyAmbient); fillLightingBorderY(lighting, 1, skyAmbient); } if (index.y == corner.y + size.y - 1) fillLightingBorderY(lighting, kLightGridChunkSizeY - 1, skyAmbient); if (index.z == corner.z) fillLightingBorderZ(lighting, 0, skyAmbient); if (index.z == corner.z + size.z - 1) fillLightingBorderZ(lighting, kLightGridChunkSizeXZ - 1, skyAmbient); } bool isChunkMoreImportant(const LightGridChunk& lhs, const LightGridChunk& rhs) { return (lhs.distancePriority < rhs.distancePriority) || (lhs.distancePriority == rhs.distancePriority && lhs.dirty > rhs.dirty); } inline int isqrt(int value) { int result = 0; while (result * result < value) result++; return result; } static bool hasShadowCastingLights(const std::vector& lights) { for (size_t i = 0; i < lights.size(); ++i) if (lights[i]->getShadowMap()) return true; return false; } Vector3int32 LightGrid::getChunkIndexForPoint(const Vector3& v) { return getChunkIndexFromGridPosition(getGridPosition(v)); } LightGridChunk* LightGrid::getChunkByIndex(const Vector3int32& index) { Vector3int32 localIndex = index - cornerChunkIndex; if (isChunkInsideGridLocal(localIndex)) return getChunkByLocalIndex(localIndex); else return NULL; } LightGridChunk* LightGrid::getChunkByLocalIndex(const Vector3int32& index) { unsigned int offset = getArrayOffset(index, chunkCount); RBXASSERT(offset < chunks.size()); return chunks[offset]; } bool LightGrid::isChunkInsideGridLocal(const Vector3int32& index) { return static_cast(index.x) < static_cast(chunkCount.x) && static_cast(index.y) < static_cast(chunkCount.y) && static_cast(index.z) < static_cast(chunkCount.z); } LightGrid* LightGrid::create(VisualEngine* visualEngine, const Vector3int32& chunkCount, TextureMode textureMode) { RBXASSERT(chunkCount.x > 0 && chunkCount.y > 0 && chunkCount.z > 0); if (textureMode != Texture_None) { shared_ptr texture; shared_ptr lookupTexture; if (textureMode == Texture_3D) { texture = visualEngine->getDevice()->createTexture(Texture::Type_3D, Texture::Format_RGBA8, chunkCount.x * kLightGridChunkSizeXZ, chunkCount.z * kLightGridChunkSizeXZ, chunkCount.y * kLightGridChunkSizeY, 1, Texture::Usage_Dynamic); } else { int gridDepth = chunkCount.y * kLightGridChunkSizeY; int gridDepthSqrt = isqrt(gridDepth); // The wrapping code only works with depth that is a square for now RBXASSERT(gridDepthSqrt * gridDepthSqrt == gridDepth); texture = visualEngine->getDevice()->createTexture(Texture::Type_2D, Texture::Format_RGBA8, chunkCount.x * kLightGridChunkSizeXZ * gridDepthSqrt, chunkCount.z * kLightGridChunkSizeXZ * gridDepthSqrt, 1, 1, Texture::Usage_Dynamic); // Create a LUT std::vector lutData; for (int slice = 0; slice < gridDepth; ++slice) { int sliceOffsetX = (slice % gridDepthSqrt); int sliceOffsetY = (slice / gridDepthSqrt); int sliceNext = (slice + 1) % gridDepth; int sliceNextOffsetX = (sliceNext % gridDepthSqrt); int sliceNextOffsetY = (sliceNext / gridDepthSqrt); lutData.push_back(static_cast(sliceOffsetX) / gridDepthSqrt); lutData.push_back(static_cast(sliceOffsetY) / gridDepthSqrt); lutData.push_back(static_cast(sliceNextOffsetX) / gridDepthSqrt); lutData.push_back(static_cast(sliceNextOffsetY) / gridDepthSqrt); } // Upload LUT to a texture if (visualEngine->getDevice()->getCaps().supportsTextureHalfFloat) { std::vector lutDataHalf; for (size_t i = 0; i < lutData.size(); ++i) lutDataHalf.push_back(floatToHalf(lutData[i])); lookupTexture = visualEngine->getDevice()->createTexture(Texture::Type_2D, Texture::Format_RGBA16F, gridDepth, 1, 1, 1, Texture::Usage_Static); lookupTexture->upload(0, 0, TextureRegion(0, 0, gridDepth, 1), &lutDataHalf[0], lutDataHalf.size() * sizeof(lutDataHalf[0])); } else { std::vector lutDataByte; for (size_t i = 0; i < lutData.size(); ++i) lutDataByte.push_back(lutData[i] * 255.f + 0.5f); lookupTexture = visualEngine->getDevice()->createTexture(Texture::Type_2D, Texture::Format_RGBA8, gridDepth, 1, 1, 1, Texture::Usage_Static); lookupTexture->upload(0, 0, TextureRegion(0, 0, gridDepth, 1), &lutDataByte[0], lutDataByte.size() * sizeof(lutDataByte[0])); } } return new LightGrid(visualEngine, texture, lookupTexture, chunkCount); } else { return new LightGrid(visualEngine, shared_ptr(), shared_ptr(), chunkCount); } } LightGrid::LightGrid(VisualEngine* visualEngine, const shared_ptr& texture, const shared_ptr& lookupTexture, const Vector3int32& chunkCount) : Resource(visualEngine->getDevice()) , visualEngine(visualEngine) { unsigned char defaultBorderGlobal = 255; unsigned char defaultBorderSkylight = 255; // Pick a sensible default centered around origin Vector3int32 corner = Vector3int32(-chunkCount.x / 2, -chunkCount.y / 2, -chunkCount.z / 2); chunks.resize(chunkCount.x * chunkCount.y * chunkCount.z); for (int cy = 0; cy < chunkCount.y; ++cy) for (int cz = 0; cz < chunkCount.z; ++cz) for (int cx = 0; cx < chunkCount.x; ++cx) { unsigned int offset = getArrayOffset(Vector3int32(cx, cy, cz), chunkCount); chunks[offset] = new LightGridChunk(); LightGridChunk& chunk = *chunks[offset]; chunk.index = corner + Vector3int32(cx, cy, cz); chunk.distancePriority = 0; chunk.dirty = 0; chunk.neverDirty = 0; chunk.age = 0; fillDummyLighting(chunk, defaultBorderGlobal, defaultBorderSkylight); } memset(dummyFringeXZ255.data, 255, sizeof(dummyFringeXZ255.data)); memset(dummyFringeY255.data, 255, sizeof(dummyFringeY255.data)); this->chunkCount = chunkCount; cornerChunkIndex = corner; this->texture = texture; this->lookupTexture = lookupTexture; lightBorderGlobal = defaultBorderGlobal; lightBorderSkylight = defaultBorderSkylight; lightShadows = false; lightDirection = Vector3(0, -1, 0); skyAmbient = Color3uint8(0, 0, 0); dirtyCursor = Vector3int32(0,0,0); dirtyCursorDirection = Vector3int32(-1,-1,-1); useSIMD = false; #ifdef SIMD_SSE2 useSIMD = true; #endif #ifdef SIMD_NEON useSIMD = true; #endif precomputeShadowLUT(); } LightGrid::~LightGrid() { for (size_t i = 0; i < chunks.size(); ++i) delete chunks[i]; } void LightGrid::setNonFixedPartsEnabled(bool value) { voxelizer.setNonFixedPartsEnabled(value); } bool LightGrid::getNonFixedPartsEnabled() const { return voxelizer.getNonFixedPartsEnabled(); } inline bool isPartFixed(RBX::PartInstance* part) { return part->getPartPrimitive()->getAnchoredProperty() || part->getPartPrimitive()->computeIsGrounded(); } void LightGrid::occupancyUpdateChunkPrepare(Voxel::OccupancyChunk& chunk, MegaClusterInstance* terrain, ContactManager* contactManager, std::vector< DataModelPartCache >& partCache) { RBXPROFILER_SCOPE("Render", "occupancyUpdateChunkPrepare"); size_t partCacheOffset = partCache.size(); voxelizer.occupancyUpdateChunkPrepare(chunk, terrain, contactManager, partCache); RBXPROFILER_LABELF("Render", "Primitives: %d", int(partCache.size() - partCacheOffset)); } void LightGrid::occupancyUpdateChunkPerform(const std::vector< DataModelPartCache >& partCache) { RBXPROFILER_SCOPE("Render", "occupancyUpdateChunkPerform"); voxelizer.occupancyUpdateChunkPerform(partCache); RBXPROFILER_LABELF("Render", "Primitives: %d", int(partCache.size())); } void LightGrid::lightingUpdateChunkLocal(LightGridChunk& chunk, SpatialHashedScene* spatialHashedScene) { RBXPROFILER_SCOPE("Render", "lightingUpdateChunkLocal"); Extents chunkExtents = chunk.getChunkExtents(); std::vector lights; lightingGetLights(lights, chunkExtents, spatialHashedScene); // Update 'never dirty' mask based on whether there are shadow casting lights in the chunk chunk.neverDirty = hasShadowCastingLights(lights) ? 0 : LightGridChunk::Dirty_LightingLocalShadowed; // Update lighting contribution in the chunk if (!lights.empty()) { // Accumulate lighting to scratch buffer memset(lightingScratch, 0, sizeof(lightingScratch)); for (size_t i = 0; i < lights.size(); ++i) lightingUpdateLightScratch(chunk, chunkExtents, lights[i]); // Separable blur along 3 axes SIMDCALL(lightingBlurAxisYZScratch, ()); SIMDCALL(lightingBlurAxisYZScratch, ()); SIMDCALL(lightingBlurAxisXScratchToChunk, (chunk)); // note: last blur step copies the lighting to chunk } else { lightingClearLocal(chunk); } } void LightGrid::lightingUpdateChunkGlobal(LightGridChunk& chunk) { RBXPROFILER_SCOPE("Render", "lightingUpdateChunkGlobal"); if (lightShadows) { lightingUpdateDirectional(chunk, lightDirection); } else { lightingClearGlobal(chunk); } } void LightGrid::lightingUpdateChunkSkylight(LightGridChunk& chunk) { RBXPROFILER_SCOPE("Render", "lightingUpdateChunkSkylight"); if (lightShadows) { lightingUpdateSkylight(chunk); } else { memset(chunk.lightingSkylight, 0, sizeof(chunk.lightingSkylight)); } } void LightGrid::lightingUpdateChunkAverage(LightGridChunk& chunk) { RBXPROFILER_SCOPE("Render", "lightingUpdateChunkAverage"); SIMDCALL(lightingUpdateAverageImpl, (chunk)); } void LightGrid::invalidateAll(unsigned int status) { for (size_t i = 0; i < chunks.size(); ++i) { LightGridChunk& chunk = *chunks[i]; chunk.dirty |= (status & ~chunk.neverDirty); } } void LightGrid::invalidateExtents(const RBX::Extents& extents, unsigned int status) { Vector3int32 chunkMin = getChunkIndexForPoint(extents.min()).max(cornerChunkIndex); Vector3int32 chunkMax = getChunkIndexForPoint(extents.max()).min(cornerChunkIndex + chunkCount - Vector3int32(1, 1, 1)); for (int y = chunkMin.y; y <= chunkMax.y; ++y) for (int z = chunkMin.z; z <= chunkMax.z; ++z) for (int x = chunkMin.x; x <= chunkMax.x; ++x) { FASTLOG4(FLog::RenderLightGrid, "LightGrid: Marking chunk %dx%dx%d as dirty (%d)", x, y, z, status); LightGridChunk* chunk = getChunkByIndex(Vector3int32(x, y, z)); RBXASSERT(chunk); chunk->dirty |= (status & ~chunk->neverDirty); } } void LightGrid::invalidatePoint(const Vector3& point, unsigned int status) { Vector3int32 chunkIndex = getChunkIndexForPoint(point); if (LightGridChunk* chunk = getChunkByIndex(chunkIndex)) { chunk->dirty |= (status & ~chunk->neverDirty); } } bool LightGrid::updateGridCenter(const Vector3& cameraTarget, bool skipChunkUpdate) { Vector3int32 cameraPos = getGridPosition(cameraTarget); Vector3int32 cameraChunk = getChunkIndexFromGridPosition(cameraPos); // If camera is in hysteresis region (2x2x2 chunks in the middle of the grid), don't relocate the grid Vector3int32 gridCenterLocal = Vector3int32(chunkCount.x / 2, chunkCount.y / 2, chunkCount.z / 2); Vector3int32 chunkCenterLocal = Vector3int32(kLightGridChunkSizeXZ / 2, kLightGridChunkSizeY / 2, kLightGridChunkSizeXZ / 2); Vector3int32 cameraChunkCenterOffset = cameraChunk - (cornerChunkIndex + gridCenterLocal); if ((cameraChunkCenterOffset.x == -1 || cameraChunkCenterOffset.x == 0) && (cameraChunkCenterOffset.y == -1 || cameraChunkCenterOffset.y == 0) && (cameraChunkCenterOffset.z == -1 || cameraChunkCenterOffset.z == 0)) { return false; } // Figure out the new location from the chunk that's nearest to the camera Vector3int32 newCorner = getChunkIndexFromGridPosition(cameraPos + chunkCenterLocal) - gridCenterLocal; RBXASSERT(newCorner != cornerChunkIndex); relocateGrid(newCorner, skipChunkUpdate); return true; } void LightGrid::updateAgePriorityForChunks(const Vector3& cameraTarget) { Vector3int32 cellPos = getGridPosition(cameraTarget); for (size_t i = 0; i < chunks.size(); ++i) { LightGridChunk& chunk = *chunks[i]; if(chunk.dirty == 0) continue; Vector3int32 chunkCenter = chunk.index * kLightGridChunkSize + Vector3int32(kLightGridChunkSizeXZ / 2, kLightGridChunkSizeY / 2, kLightGridChunkSizeXZ / 2); Vector3int32 diff = Vector3int32( abs(cellPos.x - chunkCenter.x) / kLightGridChunkSizeXZ , abs(cellPos.y - chunkCenter.y) / (kLightGridChunkSizeY/2), abs(cellPos.z - chunkCenter.z) / kLightGridChunkSizeXZ); chunk.distancePriority = std::max(diff.x, std::max(diff.y, diff.z)); chunk.age++; } } void LightGrid::updateBorderColor(const Vector3& cameraTarget, const Frustum& cameraFrustum) { // We only care about XZ frustum range, so disable far, top and bottom plane culling for chunks Frustum cameraFrustumInfinite = cameraFrustum; if (cameraFrustumInfinite.faceArray.size() > 3) { // Conveniently, the planes we care about are the first three, so we can just resize the array if it had more planes to test BOOST_STATIC_ASSERT(Frustum::kPlaneNear == 0 && Frustum::kPlaneRight == 1 && Frustum::kPlaneLeft == 2); cameraFrustumInfinite.faceArray.resize(3); } Vector3int32 cellPos = getGridPosition(cameraTarget); unsigned long long borderGlobal = 0; unsigned long long borderSkylight = 0; unsigned long long borderWeight = 0; for (size_t i = 0; i < chunks.size(); ++i) { LightGridChunk& chunk = *chunks[i]; bool isBorderXZ = !isChunkInsideGridLocal(chunk.index - cornerChunkIndex - Vector3int32(1, 0, 1)) || !isChunkInsideGridLocal(chunk.index - cornerChunkIndex + Vector3int32(1, 0, 1)); int localY = cellPos.y - chunk.index.y * kLightGridChunkSizeY; // We include an average of two Y slices above our level for all visible chunks on XZ border const int minOffsetY = 1; const int maxOffsetY = 2; if (isBorderXZ && localY + maxOffsetY >= 0 && localY + minOffsetY < kLightGridChunkSizeY) { Extents extents = chunk.getChunkExtents(); if (cameraFrustumInfinite.intersectsSphere(extents.center(), extents.size().x / 2)) { for (int offset = minOffsetY; offset <= maxOffsetY; offset++) { if (localY + offset >= 0 && localY + offset < kLightGridChunkSizeY) { borderGlobal += chunk.lightingAverageGlobal[localY + offset]; borderSkylight += chunk.lightingAverageSkylight[localY + offset]; borderWeight += chunk.lightingAverageWeight[localY + offset]; } } } } } if (borderWeight) { lightBorderGlobal = (borderGlobal + borderWeight - 1) / borderWeight; lightBorderSkylight = (borderSkylight + borderWeight - 1) / borderWeight; } } Color4uint8 LightGrid::computeAverageColor(const Vector3& focusPoint) { RBXPROFILER_SCOPE("Render", "computeAverageColor"); Vector3int32 cellPos = getGridPosition(focusPoint); int border[4] = {}; int borderCount = 0; Color3uint8 storageSkyAmbient = transformColor(skyAmbient); for (int yi = 0; yi < 3; ++yi) for (int zi = -4; zi <= 4; ++zi) for (int xi = -4; xi <= 4; ++xi) { Vector3int32 pos = cellPos + Vector3int32(xi, yi, zi); Vector3int32 cpos = getChunkIndexFromGridPosition(pos); if (LightGridChunk* chunk = getChunkByIndex(cpos)) { Vector3int32 cidx = pos - chunk->index * kLightGridChunkSize; border[0] += chunk->lighting[cidx.y][cidx.z][cidx.x][0]; border[1] += chunk->lighting[cidx.y][cidx.z][cidx.x][1]; border[2] += chunk->lighting[cidx.y][cidx.z][cidx.x][2]; if (lightShadows) { unsigned char skylight = chunk->lightingSkylight[cidx.y][cidx.z][cidx.x]; border[0] += (skylight * storageSkyAmbient.r + 255) >> 8; border[1] += (skylight * storageSkyAmbient.g + 255) >> 8; border[2] += (skylight * storageSkyAmbient.b + 255) >> 8; // border[3] accumulates skylight factor, shadow factor here intentionally, // computeAverageColor used where actual voxel data can't be displayed, // so shadow term would look weird in outdoors when you can't see the shadow border[3] += skylight; } else { border[3] += 255; } borderCount++; } } if (borderCount == 0) return transformColor(lightShadows ? Color4uint8(storageSkyAmbient, 255) : Color4uint8(0,0,0,255)); else return transformColor(Color4uint8(border[0] / borderCount, border[1] / borderCount, border[2] / borderCount, border[3] / borderCount)); } void LightGrid::setLightShadows(bool value) { if (value != lightShadows) { FASTLOG1(FLog::RenderLightGrid, "LightGrid: Changing light shadows to %d", value); lightShadows = value; invalidateAll(LightGridChunk::Dirty_LightingGlobal | LightGridChunk::Dirty_LightingSkylight); } } void LightGrid::setLightDirection(const Vector3& value) { if (value.fuzzyNe(lightDirection)) { FASTLOG3F(FLog::RenderLightGrid, "LightGrid: Changing light direction to %f %f %f", value.x, value.y, value.z); lightDirection = value; dirtyCursorDirection = Vector3int32(lightDirection.x < 0 ? -1 : 1, lightDirection.y < 0 ? -1 : 1, lightDirection.z < 0 ? -1 : 1); invalidateAll(LightGridChunk::Dirty_LightingGlobal); } } void LightGrid::setSkyAmbient(const Color3uint8& value) { if (value != skyAmbient) { FASTLOG3(FLog::RenderLightGrid, "LightGrid: Changing sky ambient to %d %d %d", value.r, value.g, value.b); skyAmbient = value; invalidateAll(LightGridChunk::Dirty_LightingUpload); } } void LightGrid::stepCursor(Vector3int32& cursor) { cursor.x += dirtyCursorDirection.x; if(cursor.x < 0 || cursor.x >= chunkCount.x) { cursor.x = dirtyCursorDirection.x < 0 ? chunkCount.x-1 : 0; cursor.z += dirtyCursorDirection.z; if(cursor.z < 0 || cursor.z >= chunkCount.z) { cursor.z = dirtyCursorDirection.z < 0 ? chunkCount.z-1 : 0; cursor.y += dirtyCursorDirection.y; if(cursor.y < 0 || cursor.y >= chunkCount.y) { cursor.y = dirtyCursorDirection.y < 0 ? chunkCount.y-1 : 0; } } } RBXASSERT(cursor.max(chunkCount) == chunkCount); RBXASSERT(cursor.min(Vector3int32(0,0,0)) == Vector3int32(0,0,0)); } LightGridChunk* LightGrid::findDirtyChunk() { LightGridChunk* candidateChunk = NULL; Vector3int32 newCursor = dirtyCursor; for (size_t i = 0; i < chunks.size(); ++i) { stepCursor(dirtyCursor); LightGridChunk* chunk = getChunkByLocalIndex(dirtyCursor); if (chunk->dirty && (candidateChunk == NULL || isChunkMoreImportant(*chunk, *candidateChunk))) { candidateChunk = chunk; newCursor = dirtyCursor; } } dirtyCursor = newCursor; return candidateChunk; } LightGridChunk* LightGrid::findFirstDirtyChunk() { for (size_t i = 0; i < chunks.size(); ++i) { stepCursor(dirtyCursor); LightGridChunk* chunk = getChunkByLocalIndex(dirtyCursor); if (chunk->dirty) return chunk; } return NULL; } LightGridChunk* LightGrid::findOldestChunk() { LightGridChunk* candidateChunk = NULL; Vector3int32 cursor = Vector3int32(0,0,0); for (size_t i = 0; i < chunks.size(); ++i) { stepCursor(cursor); LightGridChunk* chunk = getChunkByLocalIndex(cursor); if (chunk->dirty && (candidateChunk == NULL || chunk->age > candidateChunk->age)) candidateChunk = chunk; } return candidateChunk; } Vector3 LightGrid::getGridCornerOffset() const { return Vector3(cornerChunkIndex.x, cornerChunkIndex.y, cornerChunkIndex.z) * Vector3(kLightGridChunkSizeXZ, kLightGridChunkSizeY, kLightGridChunkSizeXZ) * 4.f; } Vector3 LightGrid::getWrapSafeOffset() const { Vector3int32 wrappedIndex = getWrappedChunkIndex(cornerChunkIndex); // wrappedIndex and cornerChunkIndex should map to the same location in the texture // So it should be safe to offset by the difference Vector3int32 offset = wrappedIndex - cornerChunkIndex; return Vector3(offset.x, offset.y, offset.z) * Vector3(kLightGridChunkSizeXZ, kLightGridChunkSizeY, kLightGridChunkSizeXZ) * 4.f; } Vector3 LightGrid::getGridSize() const { return Vector3(chunkCount.x, chunkCount.y, chunkCount.z) * Vector3(kLightGridChunkSizeXZ, kLightGridChunkSizeY, kLightGridChunkSizeXZ) * 4.f; } Color4uint8 LightGrid::getBorderColor() const { unsigned char global = lightBorderGlobal; unsigned char skylight = lightBorderSkylight; if (FLog::RenderLightGridBorderGlobalCutoff) global = (global < FLog::RenderLightGridBorderGlobalCutoff) ? 0 : 255; if (FLog::RenderLightGridBorderSkylightCutoff) skylight = (skylight < FLog::RenderLightGridBorderSkylightCutoff) ? 0 : 255; return Color4uint8( (skyAmbient.r * skylight + 255) >> 8, (skyAmbient.g * skylight + 255) >> 8, (skyAmbient.b * skylight + 255) >> 8, global); } void LightGrid::lightingUpdateDirectional(LightGridChunk& chunk, const Vector3& lightDirection) { bool negX = lightDirection.x < 0; bool negZ = lightDirection.z < 0; Vector3int32 lightContrib = getLightContribution(lightDirection, 8); // Run the right version of update kernel if (negX) { if (negZ) lightingUpdateDirectionalImpl(chunk, lightContrib); else lightingUpdateDirectionalImpl(chunk, lightContrib); } else { if (negZ) lightingUpdateDirectionalImpl(chunk, lightContrib); else lightingUpdateDirectionalImpl(chunk, lightContrib); } } template void LightGrid::lightingUpdateDirectionalImpl(LightGridChunk& chunk, const Vector3int32& lightContrib) { #define IRRADIANCE(x, y, z) irradianceScratch[(y)+1][(z)+1][(x)+1] RBXASSERT(lightContrib.x + lightContrib.y + lightContrib.z == 256); int stepX = NegX ? 1 : -1; int stepY = 1; int stepZ = NegZ ? 1 : -1; // Update incoming irradiance LightGridChunk* incomingIrradianceXChunk = getChunkByIndex(chunk.index + Vector3int32(stepX, 0, 0)); LightGridChunkFringeXZ& incomingIrradianceX = incomingIrradianceXChunk ? incomingIrradianceXChunk->irradianceFringeX : dummyFringeXZ255; LightGridChunk* incomingIrradianceYChunk = getChunkByIndex(chunk.index + Vector3int32(0, stepY, 0)); LightGridChunkFringeY& incomingIrradianceY = incomingIrradianceYChunk ? incomingIrradianceYChunk->irradianceFringeY : dummyFringeY255; LightGridChunk* incomingIrradianceZChunk = getChunkByIndex(chunk.index + Vector3int32(0, 0, stepZ)); LightGridChunkFringeXZ& incomingIrradianceZ = incomingIrradianceZChunk ? incomingIrradianceZChunk->irradianceFringeZ : dummyFringeXZ255; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) IRRADIANCE(NegX ? kLightGridChunkSizeXZ : -1, y, z) = incomingIrradianceX.data[y][z]; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) IRRADIANCE(x, kLightGridChunkSizeY, z) = incomingIrradianceY.data[z][x]; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) IRRADIANCE(x, y, NegZ ? kLightGridChunkSizeXZ : -1) = incomingIrradianceZ.data[y][x]; #if defined(SIMD_SSE2) || defined(SIMD_NEON) // tableXSat[i] = max(0, i - 256) * lightContrib.x unsigned short tableXSat[512]; for (int i = 0; i < 256; ++i) tableXSat[i] = 0; for (int i = 0; i < 256; ++i) tableXSat[i + 256] = i * lightContrib.x; #endif // Compute lighting for cells for (int yi = 0; yi < kLightGridChunkSizeY; ++yi) { int y = kLightGridChunkSizeY - 1 - yi; for (int zi = 0; zi < kLightGridChunkSizeXZ; ++zi) { int z = NegZ ? kLightGridChunkSizeXZ - 1 - zi : zi; #ifdef SIMD_SSE2 if (useSIMD) { __m128i contribY = _mm_set1_epi16(lightContrib.y); __m128i contribZ = _mm_set1_epi16(lightContrib.z); __m128i zero = _mm_setzero_si128(); __m128i round = _mm_set1_epi16(255); for (int xi = 0; xi < kLightGridChunkSizeXZ; xi += 16) { int x = NegX ? kLightGridChunkSizeXZ - 16 - xi : xi; __m128i occ = _mm_loadu_si128((__m128i*)&chunk.occupancy[y][z][x]); __m128i incomingY = _mm_loadu_si128((__m128i*)&IRRADIANCE(x, y+stepY, z)); __m128i incomingZ = _mm_loadu_si128((__m128i*)&IRRADIANCE(x, y, z+stepZ)); __m128i incomingY0 = _mm_unpacklo_epi8(incomingY, zero); __m128i incomingY1 = _mm_unpackhi_epi8(incomingY, zero); __m128i incomingZ0 = _mm_unpacklo_epi8(incomingZ, zero); __m128i incomingZ1 = _mm_unpackhi_epi8(incomingZ, zero); __m128i result0 = _mm_add_epi16(round, _mm_add_epi16(_mm_mullo_epi16(incomingY0, contribY), _mm_mullo_epi16(incomingZ0, contribZ))); __m128i result1 = _mm_add_epi16(round, _mm_add_epi16(_mm_mullo_epi16(incomingY1, contribY), _mm_mullo_epi16(incomingZ1, contribZ))); unsigned short result0Buf[8], result1Buf[8]; _mm_storeu_si128((__m128i*)result0Buf, result0); _mm_storeu_si128((__m128i*)result1Buf, result1); unsigned char res[16]; #define STEP(half, i) \ do { \ int r = (tableXSat[irrprev + 256] + result##half##Buf[i % 8]) >> 8; \ chunk.lighting[y][z][x+i][3] = res[i] = r; \ irrprev = r - chunk.occupancy[y][z][x+i]; \ } while (0) if (NegX) { int irrprev = IRRADIANCE(x+16, y, z); STEP(1, 15); STEP(1, 14); STEP(1, 13); STEP(1, 12); STEP(1, 11); STEP(1, 10); STEP(1, 9); STEP(1, 8); STEP(0, 7); STEP(0, 6); STEP(0, 5); STEP(0, 4); STEP(0, 3); STEP(0, 2); STEP(0, 1); STEP(0, 0); } else { int irrprev = IRRADIANCE(x-1, y, z); STEP(0, 0); STEP(0, 1); STEP(0, 2); STEP(0, 3); STEP(0, 4); STEP(0, 5); STEP(0, 6); STEP(0, 7); STEP(1, 8); STEP(1, 9); STEP(1, 10); STEP(1, 11); STEP(1, 12); STEP(1, 13); STEP(1, 14); STEP(1, 15); } #undef STEP __m128i irr = _mm_subs_epu8(_mm_loadu_si128((__m128i*)res), occ); _mm_storeu_si128((__m128i*)&IRRADIANCE(x, y, z), irr); } } else #endif #ifdef SIMD_NEON if (useSIMD) { uint8x8_t contribY = vdup_n_u8(lightContrib.y); uint8x8_t contribZ = vdup_n_u8(lightContrib.z); uint16x8_t round = vdupq_n_u16(255); for (int xi = 0; xi < kLightGridChunkSizeXZ; xi += 16) { int x = NegX ? kLightGridChunkSizeXZ - 16 - xi : xi; uint8x16_t occ = vld1q_u8(&chunk.occupancy[y][z][x]); uint8x16_t incomingY = vld1q_u8(&IRRADIANCE(x, y+stepY, z)); uint8x16_t incomingZ = vld1q_u8(&IRRADIANCE(x, y, z+stepZ)); uint8x8_t incomingY0 = vget_low_u8(incomingY); uint8x8_t incomingY1 = vget_high_u8(incomingY); uint8x8_t incomingZ0 = vget_low_u8(incomingZ); uint8x8_t incomingZ1 = vget_high_u8(incomingZ); uint16x8_t result0 = vmlal_u8(vmlal_u8(round, incomingY0, contribY), incomingZ0, contribZ); uint16x8_t result1 = vmlal_u8(vmlal_u8(round, incomingY1, contribY), incomingZ1, contribZ); unsigned short result0Buf[8], result1Buf[8]; vst1q_u16(result0Buf, result0); vst1q_u16(result1Buf, result1); unsigned char res[16]; #define STEP(half, i) \ do { \ int r = (tableXSat[irrprev + 256] + result##half##Buf[i % 8]) >> 8; \ chunk.lighting[y][z][x+i][3] = res[i] = r; \ irrprev = r - chunk.occupancy[y][z][x+i]; \ } while (0) if (NegX) { int irrprev = IRRADIANCE(x+16, y, z); STEP(1, 15); STEP(1, 14); STEP(1, 13); STEP(1, 12); STEP(1, 11); STEP(1, 10); STEP(1, 9); STEP(1, 8); STEP(0, 7); STEP(0, 6); STEP(0, 5); STEP(0, 4); STEP(0, 3); STEP(0, 2); STEP(0, 1); STEP(0, 0); } else { int irrprev = IRRADIANCE(x-1, y, z); STEP(0, 0); STEP(0, 1); STEP(0, 2); STEP(0, 3); STEP(0, 4); STEP(0, 5); STEP(0, 6); STEP(0, 7); STEP(1, 8); STEP(1, 9); STEP(1, 10); STEP(1, 11); STEP(1, 12); STEP(1, 13); STEP(1, 14); STEP(1, 15); } #undef STEP uint8x16_t irr = vqsubq_u8(vld1q_u8(res), occ); vst1q_u8(&IRRADIANCE(x, y, z), irr); } } else #endif { for (int xi = 0; xi < kLightGridChunkSizeXZ; ++xi) { int x = NegX ? kLightGridChunkSizeXZ - 1 - xi : xi; int incomingX = IRRADIANCE(x+stepX, y, z); int incomingY = IRRADIANCE(x, y+stepY, z); int incomingZ = IRRADIANCE(x, y, z+stepZ); int result = (incomingX * lightContrib.x + incomingY * lightContrib.y + incomingZ * lightContrib.z + 255) >> 8; chunk.lighting[y][z][x][3] = result; IRRADIANCE(x, y, z) = max0(result - chunk.occupancy[y][z][x]); } } } } // Update outgoing irradiance LightGridChunkFringeXZ& outgoingIrradianceX = chunk.irradianceFringeX; unsigned int outgoingIrradianceXDirty = 0; LightGridChunkFringeY& outgoingIrradianceY = chunk.irradianceFringeY; unsigned int outgoingIrradianceYDirty = 0; LightGridChunkFringeXZ& outgoingIrradianceZ = chunk.irradianceFringeZ; unsigned int outgoingIrradianceZDirty = 0; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) updateWithDirtyFlag(outgoingIrradianceX.data[y][z], IRRADIANCE(NegX ? 0 : kLightGridChunkSizeXZ - 1, y, z), outgoingIrradianceXDirty); for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) updateWithDirtyFlag(outgoingIrradianceY.data[z][x], IRRADIANCE(x, 0, z), outgoingIrradianceYDirty); for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) updateWithDirtyFlag(outgoingIrradianceZ.data[y][x], IRRADIANCE(x, y, NegZ ? 0 : kLightGridChunkSizeXZ - 1), outgoingIrradianceZDirty); if (outgoingIrradianceXDirty > kLightGridDirectionalDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index - Vector3int32(stepX, 0, 0))) neighbor->dirty |= LightGridChunk::Dirty_LightingGlobal; } if (outgoingIrradianceYDirty > kLightGridDirectionalDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index - Vector3int32(0, stepY, 0))) neighbor->dirty |= LightGridChunk::Dirty_LightingGlobal; } if (outgoingIrradianceZDirty > kLightGridDirectionalDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index - Vector3int32(0, 0, stepZ))) neighbor->dirty |= LightGridChunk::Dirty_LightingGlobal; } #undef IRRADIANCE } void LightGrid::lightingUpdateSkylight(LightGridChunk& chunk) { #define IRRADIANCE(x, y, z) irradianceScratch[(y)+1][(z)+1][(x)+1] // Power of 4 table unsigned char powerCurve[256]; for (int i = 0; i < 256; ++i) { unsigned int r1 = 255 - i; unsigned int r2 = r1 * r1; unsigned int r4 = r2 * r2; // to make sure i=0 goes to zero we have to subtract from the max value of (r4 >> 24), which is 252 powerCurve[i] = 252 - (r4 >> 24); } // Update incoming irradiance LightGridChunk* incomingIrradianceYChunk = getChunkByIndex(chunk.index + Vector3int32(0, 1, 0)); LightGridChunkFringeY& incomingIrradianceY = incomingIrradianceYChunk ? incomingIrradianceYChunk->irradianceFringeSkyY : dummyFringeY255; LightGridChunk* incomingIrradianceX0Chunk = getChunkByIndex(chunk.index + Vector3int32(-1, 0, 0)); LightGridChunkFringeXZ& incomingIrradianceX0 = incomingIrradianceX0Chunk ? incomingIrradianceX0Chunk->irradianceFringeSkyX1 : dummyFringeXZ255; LightGridChunk* incomingIrradianceX1Chunk = getChunkByIndex(chunk.index + Vector3int32(+1, 0, 0)); LightGridChunkFringeXZ& incomingIrradianceX1 = incomingIrradianceX1Chunk ? incomingIrradianceX1Chunk->irradianceFringeSkyX0 : dummyFringeXZ255; LightGridChunk* incomingIrradianceZ0Chunk = getChunkByIndex(chunk.index + Vector3int32(0, 0, -1)); LightGridChunkFringeXZ& incomingIrradianceZ0 = incomingIrradianceZ0Chunk ? incomingIrradianceZ0Chunk->irradianceFringeSkyZ1 : dummyFringeXZ255; LightGridChunk* incomingIrradianceZ1Chunk = getChunkByIndex(chunk.index + Vector3int32(0, 0, +1)); LightGridChunkFringeXZ& incomingIrradianceZ1 = incomingIrradianceZ1Chunk ? incomingIrradianceZ1Chunk->irradianceFringeSkyZ0 : dummyFringeXZ255; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { IRRADIANCE(x, kLightGridChunkSizeY, z) = incomingIrradianceY.data[z][x]; IRRADIANCE(x, kLightGridChunkSizeY, z) = incomingIrradianceY.data[z][x]; } for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) IRRADIANCE(-1, y, z) = incomingIrradianceX0.data[y][z]; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) IRRADIANCE(x, y, -1) = incomingIrradianceZ0.data[y][x]; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) IRRADIANCE(kLightGridChunkSizeXZ, y, z) = incomingIrradianceX1.data[y][z]; for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) IRRADIANCE(x, y, kLightGridChunkSizeXZ) = incomingIrradianceZ1.data[y][x]; // Fill horizontal edges for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { IRRADIANCE(x, kLightGridChunkSizeY, -1) = (IRRADIANCE(x, kLightGridChunkSizeY-1, -1) + IRRADIANCE(x, kLightGridChunkSizeY, 0) + 1) / 2; IRRADIANCE(x, kLightGridChunkSizeY, kLightGridChunkSizeXZ) = (IRRADIANCE(x, kLightGridChunkSizeY-1, kLightGridChunkSizeXZ) + IRRADIANCE(x, kLightGridChunkSizeY, kLightGridChunkSizeXZ-1) + 1) / 2; } for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { IRRADIANCE(-1, kLightGridChunkSizeY, z) = (IRRADIANCE(-1, kLightGridChunkSizeY-1, z) + IRRADIANCE(0, kLightGridChunkSizeY, z) + 1) / 2; IRRADIANCE(kLightGridChunkSizeXZ, kLightGridChunkSizeY, z) = (IRRADIANCE(kLightGridChunkSizeXZ, kLightGridChunkSizeY-1, z) + IRRADIANCE(kLightGridChunkSizeXZ-1, kLightGridChunkSizeY, z) + 1) / 2; } // Fill vertical edges for (int y = 0; y < kLightGridChunkSizeY + 1; ++y) { IRRADIANCE(-1, y, -1) = (IRRADIANCE(-1, y, 0) + IRRADIANCE(0, y, -1) + 1) / 2; IRRADIANCE(kLightGridChunkSizeXZ, y, -1) = (IRRADIANCE(kLightGridChunkSizeXZ, y, 0) + IRRADIANCE(kLightGridChunkSizeXZ-1, y, -1) + 1) / 2; IRRADIANCE(-1, y, kLightGridChunkSizeXZ) = (IRRADIANCE(-1, y, kLightGridChunkSizeXZ-1) + IRRADIANCE(0, y, kLightGridChunkSizeXZ) + 1) / 2; IRRADIANCE(kLightGridChunkSizeXZ, y, kLightGridChunkSizeXZ) = (IRRADIANCE(kLightGridChunkSizeXZ, y, kLightGridChunkSizeXZ-1) + IRRADIANCE(kLightGridChunkSizeXZ-1, y, kLightGridChunkSizeXZ) + 1) / 2; } // Compute lighting for cells for (int yi = 0; yi < kLightGridChunkSizeY; ++yi) { int y = kLightGridChunkSizeY - 1 - yi; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { SIMDCALL(lightingUpdateSkylightRow, (chunk, y, z, powerCurve)); } } // Update outgoing irradiance LightGridChunkFringeY& outgoingIrradianceY = chunk.irradianceFringeSkyY; unsigned int outgoingIrradianceYDirty = 0; LightGridChunkFringeXZ& outgoingIrradianceX0 = chunk.irradianceFringeSkyX0; unsigned int outgoingIrradianceX0Dirty = 0; LightGridChunkFringeXZ& outgoingIrradianceX1 = chunk.irradianceFringeSkyX1; unsigned int outgoingIrradianceX1Dirty = 0; LightGridChunkFringeXZ& outgoingIrradianceZ0 = chunk.irradianceFringeSkyZ0; unsigned int outgoingIrradianceZ0Dirty = 0; LightGridChunkFringeXZ& outgoingIrradianceZ1 = chunk.irradianceFringeSkyZ1; unsigned int outgoingIrradianceZ1Dirty = 0; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) updateWithDirtyFlag(outgoingIrradianceY.data[z][x], IRRADIANCE(x, 0, z), outgoingIrradianceYDirty); for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) updateWithDirtyFlag(outgoingIrradianceX0.data[y][z], IRRADIANCE(0, y, z), outgoingIrradianceX0Dirty); for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) updateWithDirtyFlag(outgoingIrradianceX1.data[y][z], IRRADIANCE(kLightGridChunkSizeXZ-1, y, z), outgoingIrradianceX1Dirty); for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) updateWithDirtyFlag(outgoingIrradianceZ0.data[y][x], IRRADIANCE(x, y, 0), outgoingIrradianceZ0Dirty); for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) updateWithDirtyFlag(outgoingIrradianceZ1.data[y][x], IRRADIANCE(x, y, kLightGridChunkSizeXZ-1), outgoingIrradianceZ1Dirty); if (outgoingIrradianceYDirty > kLightGridSkylightDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index + Vector3int32(0, -1, 0))) neighbor->dirty |= LightGridChunk::Dirty_LightingSkylight; } if (outgoingIrradianceX0Dirty > kLightGridSkylightDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index + Vector3int32(-1, 0, 0))) neighbor->dirty |= LightGridChunk::Dirty_LightingSkylight; } if (outgoingIrradianceX1Dirty > kLightGridSkylightDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index + Vector3int32(+1, 0, 0))) neighbor->dirty |= LightGridChunk::Dirty_LightingSkylight; } if (outgoingIrradianceZ0Dirty > kLightGridSkylightDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index + Vector3int32(0, 0, -1))) neighbor->dirty |= LightGridChunk::Dirty_LightingSkylight; } if (outgoingIrradianceZ1Dirty > kLightGridSkylightDirtyThreshold) { if (LightGridChunk* neighbor = getChunkByIndex(chunk.index + Vector3int32(0, 0, +1))) neighbor->dirty |= LightGridChunk::Dirty_LightingSkylight; } #undef IRRADIANCE } void LightGrid::lightingUpdateSkylightRow(LightGridChunk& chunk, int y, int z, const unsigned char* powerCurveLUT) { #define IRRADIANCE(x, y, z) irradianceScratch[(y)+1][(z)+1][(x)+1] for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { int incoming00 = IRRADIANCE(x+1, y+1, z+1); int incoming10 = IRRADIANCE(x+0, y+1, z+1); int incoming20 = IRRADIANCE(x-1, y+1, z+1); int incoming01 = IRRADIANCE(x+1, y+1, z+0); int incoming21 = IRRADIANCE(x-1, y+1, z+0); int incoming02 = IRRADIANCE(x+1, y+1, z-1); int incoming12 = IRRADIANCE(x+0, y+1, z-1); int incoming22 = IRRADIANCE(x-1, y+1, z-1); int result = (incoming00 + incoming10 + incoming20 + incoming01 + incoming21 + incoming02 + incoming12 + incoming22 + 7) >> 3; // apply power curve to make dark highlights lighter chunk.lightingSkylight[y][z][x] = powerCurveLUT[result]; IRRADIANCE(x, y, z) = max0(result - chunk.occupancy[y][z][x]); } #undef IRRADIANCE } #ifdef SIMD_SSE2 void LightGrid::lightingUpdateSkylightRowSIMD(LightGridChunk& chunk, int y, int z, const unsigned char* powerCurveLUT) { #define IRRADIANCE(x, y, z) irradianceScratch[(y)+1][(z)+1][(x)+1] __m128i zero = _mm_setzero_si128(); __m128i all255 = _mm_set1_epi8((char)255); __m128i all252 = _mm_set1_epi8((char)252); for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { __m128i occ = _mm_loadu_si128(reinterpret_cast<__m128i*>(&chunk.occupancy[y][z][x])); __m128i incoming00 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x+1, y+1, z+1)); __m128i incoming10 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x+0, y+1, z+1)); __m128i incoming20 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x-1, y+1, z+1)); __m128i incoming01 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x+1, y+1, z+0)); __m128i incoming21 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x-1, y+1, z+0)); __m128i incoming02 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x+1, y+1, z-1)); __m128i incoming12 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x+0, y+1, z-1)); __m128i incoming22 = _mm_loadu_si128((__m128i*)&IRRADIANCE(x-1, y+1, z-1)); __m128i avg0 = _mm_avg_epu8(_mm_avg_epu8(incoming10, incoming01), _mm_avg_epu8(incoming21, incoming12)); __m128i avg1 = _mm_avg_epu8(_mm_avg_epu8(incoming00, incoming20), _mm_avg_epu8(incoming02, incoming22)); __m128i avgr = _mm_avg_epu8(avg0, avg1); // apply power curve to make dark highlights lighter __m128i avgrinv = _mm_sub_epi8(all255, avgr); __m128i avgrinv1_0 = _mm_unpacklo_epi8(avgrinv, zero); __m128i avgrinv1_1 = _mm_unpackhi_epi8(avgrinv, zero); __m128i avgrinv2_0 = _mm_mullo_epi16(avgrinv1_0, avgrinv1_0); __m128i avgrinv2_1 = _mm_mullo_epi16(avgrinv1_1, avgrinv1_1); // note: mulhi gets the results right shifted by 16, an extra right shift by 8 is below __m128i avgrinv4_0 = _mm_mulhi_epu16(avgrinv2_0, avgrinv2_0); __m128i avgrinv4_1 = _mm_mulhi_epu16(avgrinv2_1, avgrinv2_1); __m128i avgrinvcurve = _mm_packus_epi16(_mm_srli_epi16(avgrinv4_0, 8), _mm_srli_epi16(avgrinv4_1, 8)); __m128i avgrcurve = _mm_sub_epi8(all252, avgrinvcurve); // store results _mm_storeu_si128((__m128i*)&chunk.lightingSkylight[y][z][x], avgrcurve); // update irradiance for next slice __m128i irr = _mm_subs_epu8(avgr, occ); _mm_storeu_si128((__m128i*)&IRRADIANCE(x,y,z), irr); } #undef IRRADIANCE } #endif #ifdef SIMD_NEON void LightGrid::lightingUpdateSkylightRowSIMD(LightGridChunk& chunk, int y, int z, const unsigned char* powerCurveLUT) { #define IRRADIANCE(x, y, z) irradianceScratch[(y)+1][(z)+1][(x)+1] uint8x16_t all255 = vdupq_n_u8(255); uint8x16_t all252 = vdupq_n_u8(252); for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { uint8x16_t occ = vld1q_u8(&chunk.occupancy[y][z][x]); uint8x16_t incoming00 = vld1q_u8(&IRRADIANCE(x+1, y+1, z+1)); uint8x16_t incoming10 = vld1q_u8(&IRRADIANCE(x+0, y+1, z+1)); uint8x16_t incoming20 = vld1q_u8(&IRRADIANCE(x-1, y+1, z+1)); uint8x16_t incoming01 = vld1q_u8(&IRRADIANCE(x+1, y+1, z+0)); uint8x16_t incoming21 = vld1q_u8(&IRRADIANCE(x-1, y+1, z+0)); uint8x16_t incoming02 = vld1q_u8(&IRRADIANCE(x+1, y+1, z-1)); uint8x16_t incoming12 = vld1q_u8(&IRRADIANCE(x+0, y+1, z-1)); uint8x16_t incoming22 = vld1q_u8(&IRRADIANCE(x-1, y+1, z-1)); uint8x16_t avg0 = vrhaddq_u8(vrhaddq_u8(incoming10, incoming01), vrhaddq_u8(incoming21, incoming12)); uint8x16_t avg1 = vrhaddq_u8(vrhaddq_u8(incoming00, incoming20), vrhaddq_u8(incoming02, incoming22)); uint8x16_t avgr = vrhaddq_u8(avg0, avg1); // apply power curve to make dark highlights lighter uint8x16_t avgrinv = vsubq_u8(all255, avgr); uint8x8_t avgrinv1_0 = vget_low_u8(avgrinv); uint8x8_t avgrinv1_1 = vget_high_u8(avgrinv); uint16x8_t avgrinv2_0 = vshrq_n_u16(vmull_u8(avgrinv1_0, avgrinv1_0), 8); uint16x8_t avgrinv2_1 = vshrq_n_u16(vmull_u8(avgrinv1_1, avgrinv1_1), 8); uint16x8_t avgrinv4_0 = vshrq_n_u16(vmulq_u16(avgrinv2_0, avgrinv2_0), 8); uint16x8_t avgrinv4_1 = vshrq_n_u16(vmulq_u16(avgrinv2_1, avgrinv2_1), 8); uint8x16_t avgrinvcurve = vcombine_u8(vmovn_u16(avgrinv4_0), vmovn_u16(avgrinv4_1)); uint8x16_t avgrcurve = vsubq_u8(all252, avgrinvcurve); // store results vst1q_u8(&chunk.lightingSkylight[y][z][x], avgrcurve); // update irradiance for next slice uint8x16_t irr = vqsubq_u8(avgr, occ); vst1q_u8(&IRRADIANCE(x,y,z), irr); } #undef IRRADIANCE } #endif void LightGrid::lightingGetLights(std::vector& lights, const Extents& chunkExtents, SpatialHashedScene* spatialHashedScene) { RBXPROFILER_SCOPE("Render", "lightingGetLights"); std::vector nodes; spatialHashedScene->queryExtents(nodes, chunkExtents, CullableSceneNode::Flags_LightObject); for (CullableSceneNode* node: nodes) { LightObject* lobj = static_cast(node); // Cull lights based on brightness if (lobj->getType() != LightObject::Type_None && lobj->getBrightness() > 0) { lights.push_back(lobj); } } } void LightGrid::lightingUpdateLightScratch(const LightGridChunk& chunk, const Extents& chunkExtents, LightObject* lobj) { RBXPROFILER_SCOPE("Render", "lightingUpdateLightScratch"); // Convert light data into chunk-local space Vector3 position = (lobj->getPosition() - chunkExtents.min()) / 4.f; Color3uint8 color = transformColor(Color3uint8(lobj->getColor())); float intensity = lobj->getBrightness(); float range = lobj->getRange() / 4; // Compute light extents in terms of a chunk with an extra 1-cell wide border Extents extents = lobj->getExtents(); Vector3int32 extentsMin = fastFloorInt32((extents.min() - chunkExtents.min()) / 4.f + Vector3(1.f, 1.f, 1.f)).max(Vector3int32(0, 0, 0)); // A small negative offset is added to extentsMax before flooring to prevent updating extra layer of cells for perfectly aligned/sized lights Vector3int32 extentsMax = fastFloorInt32((extents.max() - chunkExtents.min()) / 4.f + Vector3(0.99f, 0.99f, 0.99f)).min(kLightGridChunkSize + Vector3int32(1, 1, 1)); if (lobj->getType() == LightObject::Type_Point) { if (lobj->getShadowMap()) { lightingComputeShadowMask(chunk, extentsMin, extentsMax, position, *lobj->getShadowMap()); SIMDCALL(lightingUpdatePointLightScratch, (extentsMin, extentsMax, position, range, color, intensity)); } else { SIMDCALL(lightingUpdatePointLightScratch, (extentsMin, extentsMax, position, range, color, intensity)); } } else if (lobj->getType() == LightObject::Type_Spot) { if (lobj->getShadowMap()) { lightingComputeShadowMask(chunk, extentsMin, extentsMax, position, *lobj->getShadowMap()); SIMDCALL(lightingUpdateSpotLightScratch, (extentsMin, extentsMax, position, range, lobj->getDirection(), lobj->getAngle(), color, intensity)); } else { SIMDCALL(lightingUpdateSpotLightScratch, (extentsMin, extentsMax, position, range, lobj->getDirection(), lobj->getAngle(), color, intensity)); } } else if (lobj->getType() == LightObject::Type_Surface) { Vector4 axisU = lobj->getAxisU() / Vector4(1, 1, 1, 4); Vector4 axisV = lobj->getAxisV() / Vector4(1, 1, 1, 4); SIMDCALL(lightingUpdateSurfaceLightScratch, (extentsMin, extentsMax, position, range, lobj->getDirection(), lobj->getAngle(), axisU, axisV, color, intensity)); } else { RBXASSERT(false); } } void LightGrid::lightingComputeShadowMask(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, LightShadowMap& shadowMap) { RBXPROFILER_SCOPE("Render", "lightingComputeShadowMask"); Vector3int32 lightCell = fastFloorInt32(lightPosition); Vector3int32 extentsMinChunk = (extentsMin - Vector3int32(1, 1, 1)).max(Vector3int32(0, 0, 0)); Vector3int32 extentsMaxChunk = (extentsMax - Vector3int32(1, 1, 1)).min(kLightGridChunkSize - Vector3int32(1, 1, 1)); // Transfer shadow map data to shadow mask for all three axes lightingTransferShadowSliceToShadowMask<0>(extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceX, shadowMap.sliceX); lightingTransferShadowSliceToShadowMask<1>(extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceY0, shadowMap.sliceY1); lightingTransferShadowSliceToShadowMask<2>(extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceZ, shadowMap.sliceZ); // Update interior shadow mask lightingComputeShadowMaskXYZ(chunk, extentsMinChunk, extentsMaxChunk, lightCell, lightPosition - lightCell.toVector3()); // Transfer shadow map slice from shadow mask for all three axes lightingTransferShadowMaskToShadowSlice<0>(chunk, extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceX, shadowMap.sliceX); lightingTransferShadowMaskToShadowSlice<1>(chunk, extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceY0, shadowMap.sliceY1); lightingTransferShadowMaskToShadowSlice<2>(chunk, extentsMinChunk, extentsMaxChunk, lightCell, shadowMap.size, shadowMap.sliceZ, shadowMap.sliceZ); // The code above fills all voxels inside the chunk, but for edge cases (light intersects the outer border) it does not fill three faces out of 6 of the border, // and does not fill any edges or corners in the outer border either. Since blurring needs those let's copy/interpolate them from the data that we have. lightingFixupShadowMaskBorder(extentsMin, extentsMax, lightCell); } void LightGrid::lightingFixupShadowMaskBorder(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell) { // First, let's make sure that faces (minus edges or corners) are complete // Note that we have some face data from shadowmap transfer; lightCell checks detect those if (extentsMin.x == 0 && lightCell.x >= 0) for (int y = extentsMin.y; y <= extentsMax.y; ++y) for (int z = extentsMin.z; z <= extentsMax.z; ++z) shadowMask[y][z][0] = shadowMask[y][z][1]; if (extentsMin.y == 0 && lightCell.y >= 0) for (int z = extentsMin.z; z <= extentsMax.z; ++z) for (int x = extentsMin.x; x <= extentsMax.x; ++x) shadowMask[0][z][x] = shadowMask[1][z][x]; if (extentsMin.z == 0 && lightCell.z >= 0) for (int y = extentsMin.y; y <= extentsMax.y; ++y) for (int x = extentsMin.x; x <= extentsMax.x; ++x) shadowMask[y][0][x] = shadowMask[y][1][x]; if (extentsMax.x == kLightGridChunkSizeXZ + 1 && lightCell.x < kLightGridChunkSizeXZ) for (int y = extentsMin.y; y <= extentsMax.y; ++y) for (int z = extentsMin.z; z <= extentsMax.z; ++z) shadowMask[y][z][kLightGridChunkSizeXZ + 1] = shadowMask[y][z][kLightGridChunkSizeXZ]; if (extentsMax.y == kLightGridChunkSizeY + 1 && lightCell.y < kLightGridChunkSizeY) for (int z = extentsMin.z; z <= extentsMax.z; ++z) for (int x = extentsMin.x; x <= extentsMax.x; ++x) shadowMask[kLightGridChunkSizeY + 1][z][x] = shadowMask[kLightGridChunkSizeY][z][x]; if (extentsMax.z == kLightGridChunkSizeXZ + 1 && lightCell.z < kLightGridChunkSizeXZ) for (int y = extentsMin.y; y <= extentsMax.y; ++y) for (int x = extentsMin.x; x <= extentsMax.x; ++x) shadowMask[y][kLightGridChunkSizeXZ + 1][x] = shadowMask[y][kLightGridChunkSizeXZ][x]; // Now let's fill edges if (extentsMin.x == 0 || extentsMin.y == 0 || extentsMin.z == 0 || extentsMax.x == kLightGridChunkSizeXZ + 1 || extentsMax.y == kLightGridChunkSizeY + 1 || extentsMax.z == kLightGridChunkSizeXZ + 1) { #define SHADOW(x, y, z) shadowMask[(y)+1][(z)+1][(x)+1] // Fill horizontal edges for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { SHADOW(x, kLightGridChunkSizeY, -1) = (SHADOW(x, kLightGridChunkSizeY-1, -1) + SHADOW(x, kLightGridChunkSizeY, 0) + 1) / 2; SHADOW(x, kLightGridChunkSizeY, kLightGridChunkSizeXZ) = (SHADOW(x, kLightGridChunkSizeY-1, kLightGridChunkSizeXZ) + SHADOW(x, kLightGridChunkSizeY, kLightGridChunkSizeXZ-1) + 1) / 2; SHADOW(x, -1, -1) = (SHADOW(x, 0, -1) + SHADOW(x, -1, 0) + 1) / 2; SHADOW(x, -1, kLightGridChunkSizeXZ) = (SHADOW(x, 0, kLightGridChunkSizeXZ) + SHADOW(x, -1, kLightGridChunkSizeXZ-1) + 1) / 2; } for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { SHADOW(-1, kLightGridChunkSizeY, z) = (SHADOW(-1, kLightGridChunkSizeY-1, z) + SHADOW(0, kLightGridChunkSizeY, z) + 1) / 2; SHADOW(kLightGridChunkSizeXZ, kLightGridChunkSizeY, z) = (SHADOW(kLightGridChunkSizeXZ, kLightGridChunkSizeY-1, z) + SHADOW(kLightGridChunkSizeXZ-1, kLightGridChunkSizeY, z) + 1) / 2; SHADOW(-1, -1, z) = (SHADOW(-1, 0, z) + SHADOW(0, -1, z) + 1) / 2; SHADOW(kLightGridChunkSizeXZ, -1, z) = (SHADOW(kLightGridChunkSizeXZ, 0, z) + SHADOW(kLightGridChunkSizeXZ-1, -1, z) + 1) / 2; } // Fill vertical edges & corners for (int y = -1; y < kLightGridChunkSizeY + 1; ++y) { SHADOW(-1, y, -1) = (SHADOW(-1, y, 0) + SHADOW(0, y, -1) + 1) / 2; SHADOW(kLightGridChunkSizeXZ, y, -1) = (SHADOW(kLightGridChunkSizeXZ, y, 0) + SHADOW(kLightGridChunkSizeXZ-1, y, -1) + 1) / 2; SHADOW(-1, y, kLightGridChunkSizeXZ) = (SHADOW(-1, y, kLightGridChunkSizeXZ-1) + SHADOW(0, y, kLightGridChunkSizeXZ) + 1) / 2; SHADOW(kLightGridChunkSizeXZ, y, kLightGridChunkSizeXZ) = (SHADOW(kLightGridChunkSizeXZ, y, kLightGridChunkSizeXZ-1) + SHADOW(kLightGridChunkSizeXZ-1, y, kLightGridChunkSizeXZ) + 1) / 2; } #undef SHADOW } } template void LightGrid::lightingTransferShadowSliceToShadowMask(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, int shadowMapSize, const LightShadowSlice& shadowSlice0, const LightShadowSlice& shadowSlice1) { if (shadowMapSize == 0 || !shadowSlice0.data || !shadowSlice1.data) return; // YZX; X axis => YZ, Y axis => ZX, Z axis => YX const int PlaneU = (Axis == 1) ? 2 : 1; const int PlaneV = (Axis == 0) ? 2 : 0; if (lightCell[Axis] < 0 || lightCell[Axis] >= kLightGridChunkSize[Axis]) { const LightShadowSlice& shadowSlice = (lightCell[Axis] < 0 ? shadowSlice1 : shadowSlice0); int axisOffset = (lightCell[Axis] < 0 ? -1 : kLightGridChunkSize[Axis]); // Shadow map is odd and symmetric around light center; makes math easier RBXASSERT(shadowMapSize % 2 == 1); // Get shadow region offsets (relative to array start, not light center) int shadowOffsetU = lightCell[PlaneU] - shadowMapSize / 2; int shadowOffsetV = lightCell[PlaneV] - shadowMapSize / 2; // Intersect shadow region and extents in UV projection int minU = std::max(lightCell[PlaneU] - shadowMapSize / 2, extentsMin[PlaneU]); int minV = std::max(lightCell[PlaneV] - shadowMapSize / 2, extentsMin[PlaneV]); int maxU = std::min(lightCell[PlaneU] + shadowMapSize / 2, extentsMax[PlaneU]); int maxV = std::min(lightCell[PlaneV] + shadowMapSize / 2, extentsMax[PlaneV]); for (int u = minU; u <= maxU; ++u) { const unsigned char* shadowRow = shadowSlice.data.get() + (u - shadowOffsetU) * shadowMapSize; for (int v = minV; v <= maxV; ++v) { int y = (Axis == 1) ? axisOffset : u; int z = (Axis == 0) ? v : (Axis == 1) ? u : axisOffset; int x = (Axis == 0) ? axisOffset : v; shadowMask[y+1][z+1][x+1] = shadowRow[v - shadowOffsetV]; } } } } template void LightGrid::lightingTransferShadowMaskToShadowSlice(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, int shadowMapSize, LightShadowSlice& shadowSlice0, LightShadowSlice& shadowSlice1) { if (shadowMapSize == 0 || !shadowSlice0.data || !shadowSlice1.data) return; // YZX; X axis => YZ, Y axis => ZX, Z axis => YX const int PlaneU = (Axis == 1) ? 2 : 1; const int PlaneV = (Axis == 0) ? 2 : 0; bool intersectsMin = extentsMin[Axis] == 0 && lightCell[Axis] >= 0; bool intersectsMax = extentsMax[Axis] == kLightGridChunkSize[Axis] - 1 && lightCell[Axis] < kLightGridChunkSize[Axis]; // If there is just one shadowmap slice, there should be at most one intersection RBXASSERT(!(&shadowSlice0 == &shadowSlice1 && intersectsMin && intersectsMax)); for (int slice = 0; slice < 2; ++slice) { bool intersects = (slice == 0) ? intersectsMin : intersectsMax; if (intersects) { LightShadowSlice& shadowSlice = (slice == 0) ? shadowSlice0 : shadowSlice1; int axisOffset = (slice == 0) ? 0 : kLightGridChunkSize[Axis]-1; // Shadow map is odd and symmetric around light center; makes math easier RBXASSERT(shadowMapSize % 2 == 1); // Get shadow region offsets (relative to array start, not light center) int shadowOffsetU = lightCell[PlaneU] - shadowMapSize / 2; int shadowOffsetV = lightCell[PlaneV] - shadowMapSize / 2; // Intersect shadow region and extents in UV projection int minU = std::max(lightCell[PlaneU] - shadowMapSize / 2, extentsMin[PlaneU]); int minV = std::max(lightCell[PlaneV] - shadowMapSize / 2, extentsMin[PlaneV]); int maxU = std::min(lightCell[PlaneU] + shadowMapSize / 2, extentsMax[PlaneU]); int maxV = std::min(lightCell[PlaneV] + shadowMapSize / 2, extentsMax[PlaneV]); unsigned int dirty = 0; for (int u = minU; u <= maxU; ++u) { unsigned char* shadowRow = shadowSlice.data.get() + (u - shadowOffsetU) * shadowMapSize; for (int v = minV; v <= maxV; ++v) { int y = (Axis == 1) ? axisOffset : u; int z = (Axis == 0) ? v : (Axis == 1) ? u : axisOffset; int x = (Axis == 0) ? axisOffset : v; updateWithDirtyFlag(shadowRow[v - shadowOffsetV], shadowMask[y+1][z+1][x+1], dirty); } } if (dirty > kLightGridShadowDirtyThreshold) { Vector3int32 neighborIndex = chunk.index; neighborIndex[Axis] += (slice == 0) ? -1 : +1; if (LightGridChunk* neighbor = getChunkByIndex(neighborIndex)) neighbor->dirty |= LightGridChunk::Dirty_LightingLocalShadowed; } } } } void LightGrid::lightingComputeShadowMaskXYZ(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { if (lightCell.x >= extentsMin.x) { lightingComputeShadowMaskYZ(chunk, extentsMin, Vector3int32(std::min(extentsMax.x, lightCell.x), extentsMax.y, extentsMax.z), lightCell, lightFraction); } if (lightCell.x < extentsMax.x) { lightingComputeShadowMaskYZ(chunk, Vector3int32(std::max(extentsMin.x, lightCell.x + 1), extentsMin.y, extentsMin.z), extentsMax, lightCell, lightFraction); } } template void LightGrid::lightingComputeShadowMaskYZ(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { if (lightCell.y >= extentsMin.y) { lightingComputeShadowMaskZ(chunk, extentsMin, Vector3int32(extentsMax.x, std::min(extentsMax.y, lightCell.y), extentsMax.z), lightCell, lightFraction); } if (lightCell.y < extentsMax.y) { lightingComputeShadowMaskZ(chunk, Vector3int32(extentsMin.x, std::max(extentsMin.y, lightCell.y + 1), extentsMin.z), extentsMax, lightCell, lightFraction); } } template void LightGrid::lightingComputeShadowMaskZ(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { if (lightCell.z >= extentsMin.z) { lightingComputeShadowMask(chunk, extentsMin, Vector3int32(extentsMax.x, extentsMax.y, std::min(extentsMax.z, lightCell.z)), lightCell, lightFraction); } if (lightCell.z < extentsMax.z) { lightingComputeShadowMask(chunk, Vector3int32(extentsMin.x, extentsMin.y, std::max(extentsMin.z, lightCell.z + 1)), extentsMax, lightCell, lightFraction); } } template void LightGrid::lightingComputeShadowMask(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { // Only use smooth SIMD version since smooth scalar version is 2x slower than LUT #ifdef SIMD_SSE2 if (useSIMD) lightingComputeShadowMaskImplSmoothSIMD(chunk, extentsMin, extentsMax, lightCell, lightFraction); else #endif lightingComputeShadowMaskImplLUT(chunk, extentsMin, extentsMax, lightCell, lightFraction); } static int getLUTOffset(const Vector3int32& lightCell, int axis) { if (lightCell[axis] < 0) return -lightCell[axis]; else if (lightCell[axis] >= kLightGridChunkSize[axis]) return lightCell[axis] - kLightGridChunkSize[axis] + 1; else return 0; } template void LightGrid::lightingComputeShadowMaskImplLUT(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { #define SHADOW(x, y, z) shadowMask[(y)+1][(z)+1][(x)+1] int stepX = NegX ? 1 : -1; int stepY = NegY ? 1 : -1; int stepZ = NegZ ? 1 : -1; // Verify that all accesses in the loops don't go out of chunk bounds RBXASSERT(extentsMin.x >= 0 && extentsMin.y >= 0 && extentsMin.z >= 0); RBXASSERT(extentsMax.x < kLightGridChunkSizeXZ && extentsMax.y < kLightGridChunkSizeY && extentsMax.z < kLightGridChunkSizeXZ); // It's critical that we only use edge (xi/yi/zi == 0) values if the light source is outside the chunk int lutOffsetX = getLUTOffset(lightCell, 0); int lutOffsetY = getLUTOffset(lightCell, 1); int lutOffsetZ = getLUTOffset(lightCell, 2); Vector3int32 size = extentsMax - extentsMin; for (int yi = 0; yi <= size.y; ++yi) { int y = NegY ? extentsMax.y - yi : extentsMin.y + yi; int lutY = yi + lutOffsetY; for (int zi = 0; zi <= size.z; ++zi) { int z = NegZ ? extentsMax.z - zi : extentsMin.z + zi; int lutZ = zi + lutOffsetZ; for (int xi = 0; xi <= size.x; ++xi) { int x = NegX ? extentsMax.x - xi : extentsMin.x + xi; int lutX = xi + lutOffsetX; int shadowX = SHADOW(x + stepX, y, z); int shadowY = SHADOW(x, y + stepY, z); int shadowZ = SHADOW(x, y, z + stepZ); const ShadowLUTEntry& contrib = shadowLUT[lutY][lutZ][lutX]; int result = ((shadowX * contrib.x + shadowY * contrib.y + shadowZ * contrib.z + 127) >> 7) + contrib.add; SHADOW(x, y, z) = max0(result - chunk.occupancy[y][z][x]); } } } #undef SHADOW } template void LightGrid::lightingComputeShadowMaskImplSmooth(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { #define SHADOW(x, y, z) shadowMask[(y)+1][(z)+1][(x)+1] int stepX = NegX ? 1 : -1; int stepY = NegY ? 1 : -1; int stepZ = NegZ ? 1 : -1; // Verify that all accesses in the loops don't go out of chunk bounds RBXASSERT(extentsMin.x >= 0 && extentsMin.y >= 0 && extentsMin.z >= 0); RBXASSERT(extentsMax.x < kLightGridChunkSizeXZ && extentsMax.y < kLightGridChunkSizeY && extentsMax.z < kLightGridChunkSizeXZ); // It's critical that we only use edge (xi/yi/zi == 0) values if the light source is outside the chunk int lutOffsetX = getLUTOffset(lightCell, 0); int lutOffsetY = getLUTOffset(lightCell, 1); int lutOffsetZ = getLUTOffset(lightCell, 2); Vector3int32 size = extentsMax - extentsMin; float fractionX = NegX ? lightFraction.x : -lightFraction.x; float fractionY = NegY ? lightFraction.y : -lightFraction.y; float fractionZ = NegZ ? lightFraction.z : -lightFraction.z; for (int yi = 0; yi <= size.y; ++yi) { int y = NegY ? extentsMax.y - yi : extentsMin.y + yi; int lutY = yi + lutOffsetY; float contribY = (lutY == 0) ? 0 : (lutY + fractionY) * (lutY + fractionY); for (int zi = 0; zi <= size.z; ++zi) { int z = NegZ ? extentsMax.z - zi : extentsMin.z + zi; int lutZ = zi + lutOffsetZ; float contribZ = (lutZ == 0) ? 0 : (lutZ + fractionZ) * (lutZ + fractionZ); for (int xi = 0; xi <= size.x; ++xi) { int x = NegX ? extentsMax.x - xi : extentsMin.x + xi; int lutX = xi + lutOffsetX; float contribX = (lutX == 0) ? 0 : (lutX + fractionX) * (lutX + fractionX); int shadowX = SHADOW(x + stepX, y, z); int shadowY = SHADOW(x, y + stepY, z); int shadowZ = SHADOW(x, y, z + stepZ); float contribSum = contribX + contribY + contribZ; // We never use the 0,0,0 cell from any of (up to 8) regions of update // This is mostly to guarantee that the part owner of the light does not occlude the light if it's small int result = (lutX == 0 && lutY == 0 && lutZ == 0) ? 255 : ((shadowX * contribX + shadowY * contribY + shadowZ * contribZ) / contribSum + 0.5f); SHADOW(x, y, z) = max0(result - chunk.occupancy[y][z][x]); } } } #undef SHADOW } #ifdef SIMD_SSE2 template void LightGrid::lightingComputeShadowMaskImplSmoothSIMD(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction) { #define SHADOW(x, y, z) shadowMask[(y)+1][(z)+1][(x)+1] int stepX = NegX ? 1 : -1; int stepY = NegY ? 1 : -1; int stepZ = NegZ ? 1 : -1; // Verify that all accesses in the loops don't go out of chunk bounds RBXASSERT(extentsMin.x >= 0 && extentsMin.y >= 0 && extentsMin.z >= 0); RBXASSERT(extentsMax.x < kLightGridChunkSizeXZ && extentsMax.y < kLightGridChunkSizeY && extentsMax.z < kLightGridChunkSizeXZ); // It's critical that we only use edge (xi/yi/zi == 0) values if the light source is outside the chunk int lutOffsetX = getLUTOffset(lightCell, 0); int lutOffsetY = getLUTOffset(lightCell, 1); int lutOffsetZ = getLUTOffset(lightCell, 2); Vector3int32 size = extentsMax - extentsMin; float fractionX = NegX ? lightFraction.x : -lightFraction.x; float fractionY = NegY ? lightFraction.y : -lightFraction.y; float fractionZ = NegZ ? lightFraction.z : -lightFraction.z; __m128 contribXStart1 = _mm_add_ps( NegX ? _mm_setr_ps(4, 3, 2, 1) : _mm_setr_ps(1, 2, 3, 4), _mm_set1_ps(lutOffsetX + fractionX)); __m128 contribXAdd = _mm_set1_ps(4.f); for (int yi = 0; yi <= size.y; ++yi) { int y = NegY ? extentsMax.y - yi : extentsMin.y + yi; int lutY = yi + lutOffsetY; float contribY = (lutY == 0) ? 0 : (lutY + fractionY) * (lutY + fractionY); __m128 contribY4 = _mm_set1_ps(contribY); for (int zi = 0; zi <= size.z; ++zi) { int z = NegZ ? extentsMax.z - zi : extentsMin.z + zi; int lutZ = zi + lutOffsetZ; float contribZ = (lutZ == 0) ? 0 : (lutZ + fractionZ) * (lutZ + fractionZ); { int xi = 0; int x = NegX ? extentsMax.x - xi : extentsMin.x + xi; int lutX = xi + lutOffsetX; float contribX = (lutX == 0) ? 0 : (lutX + fractionX) * (lutX + fractionX); int shadowX = SHADOW(x + stepX, y, z); int shadowY = SHADOW(x, y + stepY, z); int shadowZ = SHADOW(x, y, z + stepZ); float contribSum = contribX + contribY + contribZ; // We never use the 0,0,0 cell from any of (up to 8) regions of update // This is mostly to guarantee that the part owner of the light does not occlude the light if it's small int result = (lutX == 0 && lutY == 0 && lutZ == 0) ? 255 : ((shadowX * contribX + shadowY * contribY + shadowZ * contribZ) / contribSum + 0.5f); SHADOW(x, y, z) = max0(result - chunk.occupancy[y][z][x]); } __m128 contribZ4 = _mm_set1_ps(contribZ); __m128 contribX = contribXStart1; for (int xi = 1; xi <= size.x; xi += 4) { int x = NegX ? extentsMax.x - xi - 3 : extentsMin.x + xi; __m128i shadowY1 = _mm_loadu_si128((const __m128i*)&SHADOW(x, y + stepY, z)); __m128i shadowY2 = _mm_unpacklo_epi8(shadowY1, _mm_setzero_si128()); __m128i shadowY3 = _mm_unpacklo_epi8(shadowY2, _mm_setzero_si128()); __m128 shadowY = _mm_cvtepi32_ps(shadowY3); __m128i shadowZ1 = _mm_loadu_si128((const __m128i*)&SHADOW(x, y, z + stepZ)); __m128i shadowZ2 = _mm_unpacklo_epi8(shadowZ1, _mm_setzero_si128()); __m128i shadowZ3 = _mm_unpacklo_epi8(shadowZ2, _mm_setzero_si128()); __m128 shadowZ = _mm_cvtepi32_ps(shadowZ3); __m128i occupancy1 = _mm_loadu_si128((const __m128i*)&chunk.occupancy[y][z][x]); __m128i occupancy2 = _mm_unpacklo_epi8(occupancy1, _mm_setzero_si128()); __m128i occupancy3 = _mm_unpacklo_epi8(occupancy2, _mm_setzero_si128()); __m128 occupancy = _mm_cvtepi32_ps(occupancy3); __m128 contribX4 = _mm_mul_ps(contribX, contribX); __m128 contribSum = _mm_add_ps(contribX4, _mm_add_ps(contribY4, contribZ4)); __m128 shadowYContrib = _mm_mul_ps(shadowY, contribY4); __m128 shadowZContrib = _mm_mul_ps(shadowZ, contribZ4); __m128 shadowYZContrib = _mm_sub_ps(_mm_div_ps(_mm_add_ps(shadowYContrib, shadowZContrib), contribSum), occupancy); __m128 contribX4Norm = _mm_div_ps(contribX4, contribSum); #define VSPLAT(v, c) _mm_shuffle_ps(v, v, _MM_SHUFFLE(c,c,c,c)) #define STEP(i) \ do { \ __m128 result = _mm_add_ss(_mm_mul_ss(shadow, VSPLAT(contribX4Norm, i)), VSPLAT(shadowYZContrib, i)); \ shadow = _mm_max_ss(result, _mm_setzero_ps()); \ SHADOW(x + i, y, z) = _mm_cvtss_si32(shadow); \ } while (0) if (NegX) { __m128 shadow = _mm_set_ss(SHADOW(x + 4, y, z)); STEP(3); STEP(2); STEP(1); STEP(0); } else { __m128 shadow = _mm_set_ss(SHADOW(x - 1, y, z)); STEP(0); STEP(1); STEP(2); STEP(3); } #undef STEP #undef VSPLAT contribX = _mm_add_ps(contribX, contribXAdd); } } } #undef SHADOW } #endif void LightGrid::precomputeShadowLUT() { for (int y = 0; y < kShadowLUTSize; ++y) for (int z = 0; z < kShadowLUTSize; ++z) for (int x = 0; x < kShadowLUTSize; ++x) { ShadowLUTEntry& e = shadowLUT[y][z][x]; Vector3 contribf = Vector3(x + 0.5f, y + 0.5f, z + 0.5f); // cancel out contributions from edges // note; it's better to do it after integer renormalization, but this is easier if (x == 0) contribf.x = 0; if (y == 0) contribf.y = 0; if (z == 0) contribf.z = 0; // Use approximate weights so that LUT and Smooth versions match Vector3int32 contrib = getLightContributionApprox(normalize(contribf), 7); // We never use the 0,0,0 cell from any of (up to 8) regions of update // This is mostly to guarantee that the part owner of the light does not occlude the light if it's small if (x == 0 && y == 0 && z == 0) { e.x = 0; e.y = 0; e.z = 0; e.add = 255; } else { e.x = contrib.x; e.y = contrib.y; e.z = contrib.z; e.add = 0; } } } template void LightGrid::lightingUpdatePointLightScratch(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Color3uint8& lightColor, float lightIntensity) { float lightRadiusInvSq = 1 / (lightRadius * lightRadius); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { for (int z = extentsMin.z; z <= extentsMax.z; ++z) { for (int x = extentsMin.x; x <= extentsMax.x; ++x) { Vector3 cellPosition = Vector3((x - 1) + 0.5f, (y - 1) + 0.5f, (z - 1) + 0.5f); float distAttenuation = 1 - (lightPosition - cellPosition).squaredLength() * lightRadiusInvSq; float attenuation = saturate(distAttenuation * lightIntensity); if (Shadows) attenuation *= shadowMask[y][z][x] / 255.f; int factor = attenuation * 65536.0f + 0.5f; int color = (((lightColor.r * factor) >> 16) << 20) + (((lightColor.g * factor) >> 16) << 10) + (((lightColor.b * factor) >> 16) << 0); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. int value = lightingScratch[y][z][x] + color; int carry = value & 0x10040100; // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) lightingScratch[y][z][x] = (value & 0xff3fcff) | (carry - (carry >> 8)); } } } } #ifdef SIMD_SSE2 template void LightGrid::lightingUpdatePointLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Color3uint8& lightColor, float lightIntensity_) { __m128 zero = _mm_setzero_ps(); __m128 one = _mm_set1_ps(1.f); __m128 half = _mm_set1_ps(0.5f); __m128 offsetX = _mm_setr_ps(-0.5f, 0.5f, 1.5f, 2.5f); __m128 lightRadiusInvSq = _mm_set1_ps(1 / (lightRadius * lightRadius)); __m128 lightIntensity = _mm_set1_ps(lightIntensity_); __m128 lightPositionX = _mm_set1_ps(lightPosition.x); __m128 lightPositionY = _mm_set1_ps(lightPosition.y); __m128 lightPositionZ = _mm_set1_ps(lightPosition.z); __m128 colorR = _mm_set1_ps(lightColor.r << 20); __m128 colorG = _mm_set1_ps(lightColor.g << 10); __m128 colorB = _mm_set1_ps(lightColor.b); __m128i maskR = _mm_set1_epi32(0xff00000); __m128i maskG = _mm_set1_epi32(0x3fc00); __m128i maskB = _mm_set1_epi32(0xff); __m128i carryMask = _mm_set1_epi32(0x10040100); __m128i mask = _mm_set1_epi32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y __m128 cellVectorY = _mm_sub_ps(_mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(y)), half), lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z __m128 cellVectorZ = _mm_sub_ps(_mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(z)), half), lightPositionZ); __m128 cellVectorYZDSq = _mm_add_ps(_mm_mul_ps(cellVectorY, cellVectorY), _mm_mul_ps(cellVectorZ, cellVectorZ)); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { __m128 cellVectorX = _mm_sub_ps(_mm_add_ps(_mm_cvtepi32_ps(_mm_set1_epi32(x)), offsetX), lightPositionX); __m128 distSquared = _mm_add_ps(_mm_mul_ps(cellVectorX, cellVectorX), cellVectorYZDSq); __m128 distAttenuation = _mm_sub_ps(one, _mm_mul_ps(distSquared, lightRadiusInvSq)); __m128 attenuation = _mm_max_ps(zero, _mm_min_ps(one, _mm_mul_ps(distAttenuation, lightIntensity))); if (Shadows) { // Load first 4 bytes and expand to 4 floats __m128i shadowMask1 = _mm_loadu_si128((__m128i*)&shadowMask[y][z][x]); __m128i shadowMask2 = _mm_unpacklo_epi8(shadowMask1, _mm_setzero_si128()); __m128i shadowMask3 = _mm_unpacklo_epi8(shadowMask2, _mm_setzero_si128()); __m128 shadowMask = _mm_cvtepi32_ps(shadowMask3); // Combine shadow mask with attenuation attenuation = _mm_mul_ps(attenuation, _mm_mul_ps(shadowMask, _mm_set1_ps(1.f / 255.f))); } __m128i chanR = _mm_and_si128(maskR, _mm_cvttps_epi32(_mm_mul_ps(colorR, attenuation))); __m128i chanG = _mm_and_si128(maskG, _mm_cvttps_epi32(_mm_mul_ps(colorG, attenuation))); __m128i chanB = _mm_and_si128(maskB, _mm_cvttps_epi32(_mm_mul_ps(colorB, attenuation))); __m128i color = _mm_or_si128(chanR, _mm_or_si128(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. __m128i lighting = _mm_loadu_si128((__m128i*)&lightingScratch[y][z][x]); __m128i value = _mm_add_epi32(lighting, color); __m128i carry = _mm_and_si128(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) __m128i result = _mm_or_si128(_mm_and_si128(value, mask), _mm_sub_epi32(carry, _mm_srli_epi32(carry, 8))); _mm_storeu_si128((__m128i*)&lightingScratch[y][z][x], result); } } } } #endif #ifdef SIMD_NEON template void LightGrid::lightingUpdatePointLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Color3uint8& lightColor, float lightIntensity_) { float32x4_t zero = vdupq_n_f32(0); float32x4_t one = vdupq_n_f32(1.f); float32x4_t half = vdupq_n_f32(0.5f); float32x4_t offsetX = {-0.5f, 0.5f, 1.5f, 2.5f}; float32x4_t lightRadiusInvSq = vdupq_n_f32(1 / (lightRadius * lightRadius)); float32x4_t lightIntensity = vdupq_n_f32(lightIntensity_); float32x4_t lightPositionX = vdupq_n_f32(lightPosition.x); float32x4_t lightPositionY = vdupq_n_f32(lightPosition.y); float32x4_t lightPositionZ = vdupq_n_f32(lightPosition.z); float32x4_t colorR = vdupq_n_f32(lightColor.r << 20); float32x4_t colorG = vdupq_n_f32(lightColor.g << 10); float32x4_t colorB = vdupq_n_f32(lightColor.b); uint32x4_t maskR = vdupq_n_u32(0xff00000); uint32x4_t maskG = vdupq_n_u32(0x3fc00); uint32x4_t maskB = vdupq_n_u32(0xff); uint32x4_t carryMask = vdupq_n_u32(0x10040100); uint32x4_t mask = vdupq_n_u32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y float32x2_t yy = vcvt_f32_s32(vdup_n_s32(y)); float32x4_t cellVectorY = vsubq_f32(vsubq_f32(vcombine_f32(yy, yy), half), lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z float32x2_t zz = vcvt_f32_s32(vdup_n_s32(z)); float32x4_t cellVectorZ = vsubq_f32(vsubq_f32(vcombine_f32(zz, zz), half), lightPositionZ); float32x4_t cellVectorYZDSq = vmlaq_f32(vmulq_f32(cellVectorY, cellVectorY), cellVectorZ, cellVectorZ); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { float32x2_t xx = vcvt_f32_s32(vdup_n_s32(x)); float32x4_t cellVectorX = vsubq_f32(vaddq_f32(vcombine_f32(xx, xx), offsetX), lightPositionX); float32x4_t distSquared = vmlaq_f32(cellVectorYZDSq, cellVectorX, cellVectorX); float32x4_t distAttenuation = vmlsq_f32(one, distSquared, lightRadiusInvSq); float32x4_t attenuation = vmaxq_f32(zero, vminq_f32(one, vmulq_f32(distAttenuation, lightIntensity))); if (Shadows) { uint16x8_t shadowMask1 = vmovl_u8(vld1_u8(&shadowMask[y][z][x])); uint32x4_t shadowMask2 = vmovl_u16(vget_low_u16(shadowMask1)); float32x4_t shadowMask = vcvtq_f32_u32(shadowMask2); attenuation = vmulq_f32(attenuation, vmulq_n_f32(shadowMask, 1.f / 255.f)); } uint32x4_t chanR = vandq_u32(maskR, vcvtq_u32_f32(vmulq_f32(colorR, attenuation))); uint32x4_t chanG = vandq_u32(maskG, vcvtq_u32_f32(vmulq_f32(colorG, attenuation))); uint32x4_t chanB = vandq_u32(maskB, vcvtq_u32_f32(vmulq_f32(colorB, attenuation))); uint32x4_t color = vorrq_u32(chanR, vorrq_u32(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. uint32x4_t lighting = vld1q_u32(&lightingScratch[y][z][x]); uint32x4_t value = vaddq_u32(lighting, color); uint32x4_t carry = vandq_u32(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) uint32x4_t result = vorrq_u32(vandq_u32(value, mask), vsubq_u32(carry, vshrq_n_u32(carry, 8))); vst1q_u32(&lightingScratch[y][z][x], result); } } } } #endif template void LightGrid::lightingUpdateSpotLightScratch(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Color3uint8& lightColor, float lightIntensity) { float lightRadiusInvSq = 1 / (lightRadius * lightRadius); float lightConeHalfAngleCos = cosf(G3D::toRadians(lightConeAngle / 2)); float lightConeAttenuationScale = lightConeHalfAngleCos < 1.f ? 1.f / (1.f - lightConeHalfAngleCos) : 0.f; for (int y = extentsMin.y; y <= extentsMax.y; ++y) { for (int z = extentsMin.z; z <= extentsMax.z; ++z) { for (int x = extentsMin.x; x <= extentsMax.x; ++x) { Vector3 cellPosition = Vector3((x - 1) + 0.5f, (y - 1) + 0.5f, (z - 1) + 0.5f); float distAttenuation = 1 - (lightPosition - cellPosition).squaredLength() * lightRadiusInvSq; float coneAttenuation = (dot(lightDirection, normalize(cellPosition - lightPosition)) - lightConeHalfAngleCos) * lightConeAttenuationScale; float attenuation = saturate(distAttenuation * std::max(0.f, coneAttenuation) * lightIntensity); if (Shadows) attenuation *= shadowMask[y][z][x] / 255.f; int factor = attenuation * 65536.0f + 0.5f; int color = (((lightColor.r * factor) >> 16) << 20) + (((lightColor.g * factor) >> 16) << 10) + (((lightColor.b * factor) >> 16) << 0); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. int value = lightingScratch[y][z][x] + color; int carry = value & 0x10040100; // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) lightingScratch[y][z][x] = (value & 0xff3fcff) | (carry - (carry >> 8)); } } } } #ifdef SIMD_SSE2 template void LightGrid::lightingUpdateSpotLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Color3uint8& lightColor, float lightIntensity_) { __m128 zero = _mm_setzero_ps(); __m128 one = _mm_set1_ps(1.f); __m128 half = _mm_set1_ps(0.5f); __m128 offsetX = _mm_setr_ps(-0.5f, 0.5f, 1.5f, 2.5f); float lightConeHalfAngleCos_ = cosf(G3D::toRadians(lightConeAngle / 2)); __m128 lightRadiusInvSq = _mm_set1_ps(1 / (lightRadius * lightRadius)); __m128 lightConeHalfAngleCos = _mm_set1_ps(lightConeHalfAngleCos_); __m128 lightConeAttenuationScale = _mm_set1_ps(lightConeHalfAngleCos_ < 1.f ? 1.f / (1.f - lightConeHalfAngleCos_) : 0.f); __m128 lightIntensity = _mm_set1_ps(lightIntensity_); __m128 lightPositionX = _mm_set1_ps(lightPosition.x); __m128 lightPositionY = _mm_set1_ps(lightPosition.y); __m128 lightPositionZ = _mm_set1_ps(lightPosition.z); __m128 lightDirectionX = _mm_set1_ps(lightDirection.x); __m128 lightDirectionY = _mm_set1_ps(lightDirection.y); __m128 lightDirectionZ = _mm_set1_ps(lightDirection.z); __m128 colorR = _mm_set1_ps(lightColor.r << 20); __m128 colorG = _mm_set1_ps(lightColor.g << 10); __m128 colorB = _mm_set1_ps(lightColor.b); __m128i maskR = _mm_set1_epi32(0xff00000); __m128i maskG = _mm_set1_epi32(0x3fc00); __m128i maskB = _mm_set1_epi32(0xff); __m128i carryMask = _mm_set1_epi32(0x10040100); __m128i mask = _mm_set1_epi32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y __m128 cellVectorY = _mm_sub_ps(_mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(y)), half), lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z __m128 cellVectorZ = _mm_sub_ps(_mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(z)), half), lightPositionZ); __m128 cellVectorYZDSq = _mm_add_ps(_mm_mul_ps(cellVectorY, cellVectorY), _mm_mul_ps(cellVectorZ, cellVectorZ)); __m128 cellVectorDotYZ = _mm_add_ps(_mm_mul_ps(cellVectorY, lightDirectionY), _mm_mul_ps(cellVectorZ, lightDirectionZ)); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { __m128 cellVectorX = _mm_sub_ps(_mm_add_ps(_mm_cvtepi32_ps(_mm_set1_epi32(x)), offsetX), lightPositionX); __m128 distSquared = _mm_add_ps(_mm_mul_ps(cellVectorX, cellVectorX), cellVectorYZDSq); __m128 distInv = _mm_rsqrt_ps(distSquared); __m128 coneDot = _mm_mul_ps(distInv, _mm_add_ps(_mm_mul_ps(lightDirectionX, cellVectorX), cellVectorDotYZ)); __m128 distAttenuation = _mm_sub_ps(one, _mm_mul_ps(distSquared, lightRadiusInvSq)); __m128 coneAttenuation = _mm_max_ps(zero, _mm_mul_ps(_mm_sub_ps(coneDot, lightConeHalfAngleCos), lightConeAttenuationScale)); __m128 attenuation = _mm_max_ps(zero, _mm_min_ps(one, _mm_mul_ps(distAttenuation, _mm_mul_ps(coneAttenuation, lightIntensity)))); if (Shadows) { // Load first 4 bytes and expand to 4 floats __m128i shadowMask1 = _mm_loadu_si128((__m128i*)&shadowMask[y][z][x]); __m128i shadowMask2 = _mm_unpacklo_epi8(shadowMask1, _mm_setzero_si128()); __m128i shadowMask3 = _mm_unpacklo_epi8(shadowMask2, _mm_setzero_si128()); __m128 shadowMask = _mm_cvtepi32_ps(shadowMask3); // Combine shadow mask with attenuation attenuation = _mm_mul_ps(attenuation, _mm_mul_ps(shadowMask, _mm_set1_ps(1.f / 255.f))); } __m128i chanR = _mm_and_si128(maskR, _mm_cvttps_epi32(_mm_mul_ps(colorR, attenuation))); __m128i chanG = _mm_and_si128(maskG, _mm_cvttps_epi32(_mm_mul_ps(colorG, attenuation))); __m128i chanB = _mm_and_si128(maskB, _mm_cvttps_epi32(_mm_mul_ps(colorB, attenuation))); __m128i color = _mm_or_si128(chanR, _mm_or_si128(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. __m128i lighting = _mm_loadu_si128((__m128i*)&lightingScratch[y][z][x]); __m128i value = _mm_add_epi32(lighting, color); __m128i carry = _mm_and_si128(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) __m128i result = _mm_or_si128(_mm_and_si128(value, mask), _mm_sub_epi32(carry, _mm_srli_epi32(carry, 8))); _mm_storeu_si128((__m128i*)&lightingScratch[y][z][x], result); } } } } #endif #ifdef SIMD_NEON template void LightGrid::lightingUpdateSpotLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Color3uint8& lightColor, float lightIntensity_) { float32x4_t zero = vdupq_n_f32(0); float32x4_t one = vdupq_n_f32(1.f); float32x4_t half = vdupq_n_f32(0.5f); float32x4_t offsetX = {-0.5f, 0.5f, 1.5f, 2.5f}; float lightConeHalfAngleCos_ = cosf(G3D::toRadians(lightConeAngle / 2)); float32x4_t lightConeHalfAngleCos = vdupq_n_f32(lightConeHalfAngleCos_); float32x4_t lightConeAttenuationScale = vdupq_n_f32(lightConeHalfAngleCos_ < 1.f ? 1.f / (1.f - lightConeHalfAngleCos_) : 0.f); float32x4_t lightRadiusInvSq = vdupq_n_f32(1 / (lightRadius * lightRadius)); float32x4_t lightIntensity = vdupq_n_f32(lightIntensity_); float32x4_t lightPositionX = vdupq_n_f32(lightPosition.x); float32x4_t lightPositionY = vdupq_n_f32(lightPosition.y); float32x4_t lightPositionZ = vdupq_n_f32(lightPosition.z); float32x4_t lightDirectionX = vdupq_n_f32(lightDirection.x); float32x4_t lightDirectionY = vdupq_n_f32(lightDirection.y); float32x4_t lightDirectionZ = vdupq_n_f32(lightDirection.z); float32x4_t colorR = vdupq_n_f32(lightColor.r << 20); float32x4_t colorG = vdupq_n_f32(lightColor.g << 10); float32x4_t colorB = vdupq_n_f32(lightColor.b); uint32x4_t maskR = vdupq_n_u32(0xff00000); uint32x4_t maskG = vdupq_n_u32(0x3fc00); uint32x4_t maskB = vdupq_n_u32(0xff); uint32x4_t carryMask = vdupq_n_u32(0x10040100); uint32x4_t mask = vdupq_n_u32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y float32x2_t yy = vcvt_f32_s32(vdup_n_s32(y)); float32x4_t cellVectorY = vsubq_f32(vsubq_f32(vcombine_f32(yy, yy), half), lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z float32x2_t zz = vcvt_f32_s32(vdup_n_s32(z)); float32x4_t cellVectorZ = vsubq_f32(vsubq_f32(vcombine_f32(zz, zz), half), lightPositionZ); float32x4_t cellVectorYZDSq = vmlaq_f32(vmulq_f32(cellVectorY, cellVectorY), cellVectorZ, cellVectorZ); float32x4_t cellVectorDotYZ = vmlaq_f32(vmulq_f32(cellVectorY, lightDirectionY), cellVectorZ, lightDirectionZ); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { float32x2_t xx = vcvt_f32_s32(vdup_n_s32(x)); float32x4_t cellVectorX = vsubq_f32(vaddq_f32(vcombine_f32(xx, xx), offsetX), lightPositionX); float32x4_t distSquared = vmlaq_f32(cellVectorYZDSq, cellVectorX, cellVectorX); float32x4_t distInv = vrsqrteq_f32(distSquared); float32x4_t coneDot = vmulq_f32(distInv, vmlaq_f32(cellVectorDotYZ, lightDirectionX, cellVectorX)); float32x4_t distAttenuation = vmlsq_f32(one, distSquared, lightRadiusInvSq); float32x4_t coneAttenuation = vmaxq_f32(zero, vmulq_f32(vsubq_f32(coneDot, lightConeHalfAngleCos), lightConeAttenuationScale)); float32x4_t attenuation = vmaxq_f32(zero, vminq_f32(one, vmulq_f32(distAttenuation, vmulq_f32(coneAttenuation, lightIntensity)))); if (Shadows) { uint16x8_t shadowMask1 = vmovl_u8(vld1_u8(&shadowMask[y][z][x])); uint32x4_t shadowMask2 = vmovl_u16(vget_low_u16(shadowMask1)); float32x4_t shadowMask = vcvtq_f32_u32(shadowMask2); attenuation = vmulq_f32(attenuation, vmulq_n_f32(shadowMask, 1.f / 255.f)); } uint32x4_t chanR = vandq_u32(maskR, vcvtq_u32_f32(vmulq_f32(colorR, attenuation))); uint32x4_t chanG = vandq_u32(maskG, vcvtq_u32_f32(vmulq_f32(colorG, attenuation))); uint32x4_t chanB = vandq_u32(maskB, vcvtq_u32_f32(vmulq_f32(colorB, attenuation))); uint32x4_t color = vorrq_u32(chanR, vorrq_u32(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. uint32x4_t lighting = vld1q_u32(&lightingScratch[y][z][x]); uint32x4_t value = vaddq_u32(lighting, color); uint32x4_t carry = vandq_u32(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) uint32x4_t result = vorrq_u32(vandq_u32(value, mask), vsubq_u32(carry, vshrq_n_u32(carry, 8))); vst1q_u32(&lightingScratch[y][z][x], result); } } } } #endif template void LightGrid::lightingUpdateSurfaceLightScratch(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Vector4& axisU, const Vector4& axisV, const Color3uint8& lightColor, float lightIntensity) { float lightRadiusInvSq = 1 / (lightRadius * lightRadius); float lightConeHalfAngleCos = cosf(G3D::toRadians(lightConeAngle / 2)); float lightConeAttenuationScale = lightConeHalfAngleCos < 1.f ? 1.f / (1.f - lightConeHalfAngleCos) : 0.f; for (int y = extentsMin.y; y <= extentsMax.y; ++y) { for (int z = extentsMin.z; z <= extentsMax.z; ++z) { for (int x = extentsMin.x; x <= extentsMax.x; ++x) { Vector3 cellPosition = Vector3((x - 1) + 0.5f, (y - 1) + 0.5f, (z - 1) + 0.5f); // Clamped projection of cellPosition onto a rectangle defined by lightPosition and axisU/axisV float projU = dot(cellPosition - lightPosition, axisU.xyz()); float projUClamped = G3D::clamp(projU, -axisU.w, axisU.w); float projV = dot(cellPosition - lightPosition, axisV.xyz()); float projVClamped = G3D::clamp(projV, -axisV.w, axisV.w); Vector3 lightProjectedPosition = lightPosition + projUClamped * axisU.xyz() + projVClamped * axisV.xyz(); float distAttenuation = 1 - (lightProjectedPosition - cellPosition).squaredLength() * lightRadiusInvSq; float coneAttenuation = (dot(lightDirection, normalize(cellPosition - lightProjectedPosition)) - lightConeHalfAngleCos) * lightConeAttenuationScale; float attenuation = saturate(distAttenuation * std::max(0.f, coneAttenuation) * lightIntensity); if (Shadows) attenuation *= shadowMask[y][z][x] / 255.f; int factor = attenuation * 65536.0f + 0.5f; int color = (((lightColor.r * factor) >> 16) << 20) + (((lightColor.g * factor) >> 16) << 10) + (((lightColor.b * factor) >> 16) << 0); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. int value = lightingScratch[y][z][x] + color; int carry = value & 0x10040100; // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) lightingScratch[y][z][x] = (value & 0xff3fcff) | (carry - (carry >> 8)); } } } } #ifdef SIMD_SSE2 template void LightGrid::lightingUpdateSurfaceLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Vector4& axisU, const Vector4& axisV, const Color3uint8& lightColor, float lightIntensity_) { __m128 zero = _mm_setzero_ps(); __m128 one = _mm_set1_ps(1.f); __m128 half = _mm_set1_ps(0.5f); __m128 offsetX = _mm_setr_ps(-0.5f, 0.5f, 1.5f, 2.5f); float lightConeHalfAngleCos_ = cosf(G3D::toRadians(lightConeAngle / 2)); __m128 lightRadiusInvSq = _mm_set1_ps(1 / (lightRadius * lightRadius)); __m128 lightConeHalfAngleCos = _mm_set1_ps(lightConeHalfAngleCos_); __m128 lightConeAttenuationScale = _mm_set1_ps(lightConeHalfAngleCos_ < 1.f ? 1.f / (1.f - lightConeHalfAngleCos_) : 0.f); __m128 lightIntensity = _mm_set1_ps(lightIntensity_); __m128 lightPositionX = _mm_set1_ps(lightPosition.x); __m128 lightPositionY = _mm_set1_ps(lightPosition.y); __m128 lightPositionZ = _mm_set1_ps(lightPosition.z); __m128 lightDirectionX = _mm_set1_ps(lightDirection.x); __m128 lightDirectionY = _mm_set1_ps(lightDirection.y); __m128 lightDirectionZ = _mm_set1_ps(lightDirection.z); __m128 axisUX = _mm_set1_ps(axisU.x); __m128 axisUY = _mm_set1_ps(axisU.y); __m128 axisUZ = _mm_set1_ps(axisU.z); __m128 axisULength = _mm_set1_ps(axisU.w); __m128 axisUInvLength = _mm_set1_ps(-axisU.w); __m128 axisVX = _mm_set1_ps(axisV.x); __m128 axisVY = _mm_set1_ps(axisV.y); __m128 axisVZ = _mm_set1_ps(axisV.z); __m128 axisVLength = _mm_set1_ps(axisV.w); __m128 axisVInvLength = _mm_set1_ps(-axisV.w); __m128 colorR = _mm_set1_ps(lightColor.r << 20); __m128 colorG = _mm_set1_ps(lightColor.g << 10); __m128 colorB = _mm_set1_ps(lightColor.b); __m128i maskR = _mm_set1_epi32(0xff00000); __m128i maskG = _mm_set1_epi32(0x3fc00); __m128i maskB = _mm_set1_epi32(0xff); __m128i carryMask = _mm_set1_epi32(0x10040100); __m128i mask = _mm_set1_epi32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y __m128 yy = _mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(y)), half); __m128 cellVectorY = _mm_sub_ps(yy, lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z __m128 zz = _mm_sub_ps(_mm_cvtepi32_ps(_mm_set1_epi32(z)), half); __m128 cellVectorZ = _mm_sub_ps(zz, lightPositionZ); __m128 projUYZ = _mm_add_ps(_mm_mul_ps(axisUY, cellVectorY), _mm_mul_ps(axisUZ, cellVectorZ)); __m128 projVYZ = _mm_add_ps(_mm_mul_ps(axisVY, cellVectorY), _mm_mul_ps(axisVZ, cellVectorZ)); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { __m128 xx = _mm_add_ps(_mm_cvtepi32_ps(_mm_set1_epi32(x)), offsetX); __m128 cellVectorX = _mm_sub_ps(xx, lightPositionX); // clamp(dot(axisU,cellVector), -axisULength, +axisULength) __m128 projU = _mm_add_ps(projUYZ, _mm_mul_ps(axisUX, cellVectorX)); __m128 projUDistClamped = _mm_min_ps(_mm_max_ps(axisUInvLength, projU), axisULength); __m128 projV = _mm_add_ps(projVYZ, _mm_mul_ps(axisVX, cellVectorX)); __m128 projVDistClamped = _mm_min_ps(_mm_max_ps(axisVInvLength, projV), axisVLength); __m128 cellVectorXProjected = _mm_sub_ps(cellVectorX, _mm_add_ps(_mm_mul_ps(projUDistClamped, axisUX), _mm_mul_ps(projVDistClamped, axisVX))); __m128 cellVectorYProjected = _mm_sub_ps(cellVectorY, _mm_add_ps(_mm_mul_ps(projUDistClamped, axisUY), _mm_mul_ps(projVDistClamped, axisVY))); __m128 cellVectorZProjected = _mm_sub_ps(cellVectorZ, _mm_add_ps(_mm_mul_ps(projUDistClamped, axisUZ), _mm_mul_ps(projVDistClamped, axisVZ))); __m128 cellVectorYZDSqProjected = _mm_add_ps(_mm_mul_ps(cellVectorYProjected, cellVectorYProjected), _mm_mul_ps(cellVectorZProjected, cellVectorZProjected)); __m128 distSquared = _mm_add_ps(_mm_mul_ps(cellVectorXProjected, cellVectorXProjected), cellVectorYZDSqProjected); __m128 distInv = _mm_rsqrt_ps(distSquared); __m128 cellVectorDotYZProjected = _mm_add_ps(_mm_mul_ps(cellVectorYProjected, lightDirectionY), _mm_mul_ps(cellVectorZProjected, lightDirectionZ)); __m128 coneDot = _mm_mul_ps(distInv, _mm_add_ps(_mm_mul_ps(lightDirectionX, cellVectorXProjected), cellVectorDotYZProjected)); __m128 distAttenuation = _mm_sub_ps(one, _mm_mul_ps(distSquared, lightRadiusInvSq)); __m128 coneAttenuation = _mm_max_ps(zero, _mm_mul_ps(_mm_sub_ps(coneDot, lightConeHalfAngleCos), lightConeAttenuationScale)); __m128 attenuation = _mm_max_ps(zero, _mm_min_ps(one, _mm_mul_ps(distAttenuation, _mm_mul_ps(coneAttenuation, lightIntensity)))); if (Shadows) { // Load first 4 bytes and expand to 4 floats __m128i shadowMask1 = _mm_loadu_si128((__m128i*)&shadowMask[y][z][x]); __m128i shadowMask2 = _mm_unpacklo_epi8(shadowMask1, _mm_setzero_si128()); __m128i shadowMask3 = _mm_unpacklo_epi8(shadowMask2, _mm_setzero_si128()); __m128 shadowMask = _mm_cvtepi32_ps(shadowMask3); // Combine shadow mask with attenuation attenuation = _mm_mul_ps(attenuation, _mm_mul_ps(shadowMask, _mm_set1_ps(1.f / 255.f))); } __m128i chanR = _mm_and_si128(maskR, _mm_cvttps_epi32(_mm_mul_ps(colorR, attenuation))); __m128i chanG = _mm_and_si128(maskG, _mm_cvttps_epi32(_mm_mul_ps(colorG, attenuation))); __m128i chanB = _mm_and_si128(maskB, _mm_cvttps_epi32(_mm_mul_ps(colorB, attenuation))); __m128i color = _mm_or_si128(chanR, _mm_or_si128(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. __m128i lighting = _mm_loadu_si128((__m128i*)&lightingScratch[y][z][x]); __m128i value = _mm_add_epi32(lighting, color); __m128i carry = _mm_and_si128(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) __m128i result = _mm_or_si128(_mm_and_si128(value, mask), _mm_sub_epi32(carry, _mm_srli_epi32(carry, 8))); _mm_storeu_si128((__m128i*)&lightingScratch[y][z][x], result); } } } } #endif #ifdef SIMD_NEON template void LightGrid::lightingUpdateSurfaceLightScratchSIMD(const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3& lightPosition, float lightRadius, const Vector3& lightDirection, float lightConeAngle, const Vector4& axisU, const Vector4& axisV, const Color3uint8& lightColor, float lightIntensity_) { float32x4_t zero = vdupq_n_f32(0); float32x4_t one = vdupq_n_f32(1.f); float32x4_t half = vdupq_n_f32(0.5f); float32x4_t offsetX = {-0.5f, 0.5f, 1.5f, 2.5f}; float lightConeHalfAngleCos_ = cosf(G3D::toRadians(lightConeAngle / 2)); float32x4_t lightConeHalfAngleCos = vdupq_n_f32(lightConeHalfAngleCos_); float32x4_t lightConeAttenuationScale = vdupq_n_f32(lightConeHalfAngleCos_ < 1.f ? 1.f / (1.f - lightConeHalfAngleCos_) : 0.f); float32x4_t lightRadiusInvSq = vdupq_n_f32(1 / (lightRadius * lightRadius)); float32x4_t lightIntensity = vdupq_n_f32(lightIntensity_); float32x4_t lightPositionX = vdupq_n_f32(lightPosition.x); float32x4_t lightPositionY = vdupq_n_f32(lightPosition.y); float32x4_t lightPositionZ = vdupq_n_f32(lightPosition.z); float32x4_t lightDirectionX = vdupq_n_f32(lightDirection.x); float32x4_t lightDirectionY = vdupq_n_f32(lightDirection.y); float32x4_t lightDirectionZ = vdupq_n_f32(lightDirection.z); float32x4_t axisUX = vdupq_n_f32(axisU.x); float32x4_t axisUY = vdupq_n_f32(axisU.y); float32x4_t axisUZ = vdupq_n_f32(axisU.z); float32x4_t axisULength = vdupq_n_f32(axisU.w); float32x4_t axisUInvLength = vdupq_n_f32(-axisU.w); float32x4_t axisVX = vdupq_n_f32(axisV.x); float32x4_t axisVY = vdupq_n_f32(axisV.y); float32x4_t axisVZ = vdupq_n_f32(axisV.z); float32x4_t axisVLength = vdupq_n_f32(axisV.w); float32x4_t axisVInvLength = vdupq_n_f32(-axisV.w); float32x4_t colorR = vdupq_n_f32(lightColor.r << 20); float32x4_t colorG = vdupq_n_f32(lightColor.g << 10); float32x4_t colorB = vdupq_n_f32(lightColor.b); uint32x4_t maskR = vdupq_n_u32(0xff00000); uint32x4_t maskG = vdupq_n_u32(0x3fc00); uint32x4_t maskB = vdupq_n_u32(0xff); uint32x4_t carryMask = vdupq_n_u32(0x10040100); uint32x4_t mask = vdupq_n_u32(0xff3fcff); for (int y = extentsMin.y; y <= extentsMax.y; ++y) { // lightPosition.y - cellPosition.y float32x2_t yy = vcvt_f32_s32(vdup_n_s32(y)); float32x4_t cellY = vsubq_f32(vcombine_f32(yy, yy), half); float32x4_t cellVectorY = vsubq_f32(cellY, lightPositionY); for (int z = extentsMin.z; z <= extentsMax.z; ++z) { // lightPosition.z - cellPosition.z float32x2_t zz = vcvt_f32_s32(vdup_n_s32(z)); float32x4_t cellZ = vsubq_f32(vcombine_f32(zz, zz), half); float32x4_t cellVectorZ = vsubq_f32(cellZ, lightPositionZ); float32x4_t projUYZ = vaddq_f32(vmulq_f32(axisUY, cellVectorY), vmulq_f32(axisUZ, cellVectorZ)); float32x4_t projVYZ = vaddq_f32(vmulq_f32(axisVY, cellVectorY), vmulq_f32(axisVZ, cellVectorZ)); for (int x = extentsMin.x; x <= extentsMax.x; x += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { float32x2_t xx = vcvt_f32_s32(vdup_n_s32(x)); float32x4_t cellX = vaddq_f32(vcombine_f32(xx, xx), offsetX); float32x4_t cellVectorX = vsubq_f32(cellX, lightPositionX); float32x4_t projU = vaddq_f32(projUYZ, vmulq_f32(axisUX, cellVectorX)); float32x4_t projUDistClamped = vminq_f32(vmaxq_f32(axisUInvLength, projU), axisULength); float32x4_t projV = vaddq_f32(projVYZ, vmulq_f32(axisVX, cellVectorX)); float32x4_t projVDistClamped = vminq_f32(vmaxq_f32(axisVInvLength, projV), axisVLength); float32x4_t cellVectorXProjected = vsubq_f32(cellVectorX, vmlaq_f32(vmulq_f32(projUDistClamped, axisUX), projVDistClamped, axisVX)); float32x4_t cellVectorYProjected = vsubq_f32(cellVectorY, vmlaq_f32(vmulq_f32(projUDistClamped, axisUY), projVDistClamped, axisVY)); float32x4_t cellVectorZProjected = vsubq_f32(cellVectorZ, vmlaq_f32(vmulq_f32(projUDistClamped, axisUZ), projVDistClamped, axisVZ)); float32x4_t cellVectorYZDSqProjected = vmlaq_f32(vmulq_f32(cellVectorYProjected, cellVectorYProjected), cellVectorZProjected, cellVectorZProjected); float32x4_t distSquared = vmlaq_f32(cellVectorYZDSqProjected, cellVectorXProjected, cellVectorXProjected); float32x4_t distInv = vrsqrteq_f32(distSquared); float32x4_t cellVectorDotYZProjected = vmlaq_f32(vmulq_f32(cellVectorYProjected, lightDirectionY), cellVectorZProjected, lightDirectionZ); float32x4_t coneDot = vmulq_f32(distInv, vmlaq_f32(cellVectorDotYZProjected, lightDirectionX, cellVectorXProjected)); float32x4_t distAttenuation = vmlsq_f32(one, distSquared, lightRadiusInvSq); float32x4_t coneAttenuation = vmaxq_f32(zero, vmulq_f32(vsubq_f32(coneDot, lightConeHalfAngleCos), lightConeAttenuationScale)); float32x4_t attenuation = vmaxq_f32(zero, vminq_f32(one, vmulq_f32(distAttenuation, vmulq_f32(coneAttenuation, lightIntensity)))); if (Shadows) { uint16x8_t shadowMask1 = vmovl_u8(vld1_u8(&shadowMask[y][z][x])); uint32x4_t shadowMask2 = vmovl_u16(vget_low_u16(shadowMask1)); float32x4_t shadowMask = vcvtq_f32_u32(shadowMask2); attenuation = vmulq_f32(attenuation, vmulq_n_f32(shadowMask, 1.f / 255.f)); } uint32x4_t chanR = vandq_u32(maskR, vcvtq_u32_f32(vmulq_f32(colorR, attenuation))); uint32x4_t chanG = vandq_u32(maskG, vcvtq_u32_f32(vmulq_f32(colorG, attenuation))); uint32x4_t chanB = vandq_u32(maskB, vcvtq_u32_f32(vmulq_f32(colorB, attenuation))); uint32x4_t color = vorrq_u32(chanR, vorrq_u32(chanG, chanB)); // We're adding two values with a possibility of overflow. Carry bits are 0, so overflow will go into the first carry bit. uint32x4_t lighting = vld1q_u32(&lightingScratch[y][z][x]); uint32x4_t value = vaddq_u32(lighting, color); uint32x4_t carry = vandq_u32(value, carryMask); // Form a mask of 8 ones for each carry bit that is set (10...0 -> 10...0 - 1 = 01...1) uint32x4_t result = vorrq_u32(vandq_u32(value, mask), vsubq_u32(carry, vshrq_n_u32(carry, 8))); vst1q_u32(&lightingScratch[y][z][x], result); } } } } #endif template void LightGrid::lightingBlurAxisYZScratch() { #define LIGHTING(k) (AxisY ? lightingScratch[k][i][j] : lightingScratch[i][k][j]) for (int i = 0; i < (AxisY ? kLightGridChunkSizeXZ : kLightGridChunkSizeY) + 2; ++i) for (int j = 0; j < kLightGridChunkSizeXZ + 2; ++j) { unsigned int prev = LIGHTING(0); unsigned int cur = LIGHTING(1); for (int k = 1; k < (AxisY ? kLightGridChunkSizeY : kLightGridChunkSizeXZ) + 1; ++k) { unsigned int newCur = LIGHTING(k+1); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: do /4 instead of /3 since can't really do /3 fast enough in packed representation LIGHTING(k) = ((prev + cur + newCur) / 4) & 0xff3fcff; prev = cur; cur = newCur; } } #undef LIGHTING } #ifdef SIMD_SSE2 template void LightGrid::lightingBlurAxisYZScratchSIMD() { #define LIGHTING(k) (AxisY ? lightingScratch[k][i][j] : lightingScratch[i][k][j]) __m128i mask = _mm_set1_epi32(0xff3fcff); for (int i = 0; i < (AxisY ? kLightGridChunkSizeXZ : kLightGridChunkSizeY) + 2; ++i) for (int j = 0; j < kLightGridChunkSizeXZ + 2; j += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { __m128i prev = _mm_loadu_si128((__m128i*)&LIGHTING(0)); __m128i cur = _mm_loadu_si128((__m128i*)&LIGHTING(1)); for (int k = 1; k < (AxisY ? kLightGridChunkSizeY : kLightGridChunkSizeXZ) + 1; ++k) { __m128i newCur = _mm_loadu_si128((__m128i*)&LIGHTING(k+1)); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: do /4 instead of /3 since can't really do /3 fast enough in packed representation __m128i value = _mm_and_si128(_mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(prev, cur), newCur), 2), mask); _mm_storeu_si128((__m128i*)&LIGHTING(k), value); prev = cur; cur = newCur; } } #undef LIGHTING } #endif #ifdef SIMD_NEON template void LightGrid::lightingBlurAxisYZScratchSIMD() { #define LIGHTING(k) (AxisY ? lightingScratch[k][i][j] : lightingScratch[i][k][j]) uint32x4_t mask = vdupq_n_u32(0xff3fcff); for (int i = 0; i < (AxisY ? kLightGridChunkSizeXZ : kLightGridChunkSizeY) + 2; ++i) for (int j = 0; j < kLightGridChunkSizeXZ + 2; j += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { uint32x4_t prev = vld1q_u32(&LIGHTING(0)); uint32x4_t cur = vld1q_u32(&LIGHTING(1)); for (int k = 1; k < (AxisY ? kLightGridChunkSizeY : kLightGridChunkSizeXZ) + 1; ++k) { uint32x4_t newCur = vld1q_u32(&LIGHTING(k+1)); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: do /4 instead of /3 since can't really do /3 fast enough in packed representation uint32x4_t value = vandq_u32(vshrq_n_u32(vaddq_u32(vaddq_u32(prev, cur), newCur), 2), mask); vst1q_u32(&LIGHTING(k), value); prev = cur; cur = newCur; } } #undef LIGHTING } #endif void LightGrid::lightingBlurAxisXScratchToChunk(LightGridChunk& chunk) { #define LIGHTING(k) lightingScratch[i][j][k] for (int i = 1; i < kLightGridChunkSizeY + 1; ++i) for (int j = 1; j < kLightGridChunkSizeXZ + 1; ++j) { unsigned int prev = LIGHTING(0); unsigned int cur = LIGHTING(1); for (int k = 1; k < kLightGridChunkSizeXZ + 1; ++k) { unsigned int newCur = LIGHTING(k+1); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: we had two steps before, and we're doing the 3rd blur step; the filter weights should be 1/27 // Due to fixed point restriction, we divided by 4 in first two steps, yielding a weight of 1/16 // Divide by two here to have a weight of 1/32, which is close enough to 1/27 unsigned int value = ((prev + cur + newCur) / 2) & 0xff3fcff; chunk.lighting[i-1][j-1][k-1][0] = value >> 20; chunk.lighting[i-1][j-1][k-1][1] = value >> 10; chunk.lighting[i-1][j-1][k-1][2] = value >> 0; prev = cur; cur = newCur; } } #undef LIGHTING } #ifdef SIMD_SSE2 void LightGrid::lightingBlurAxisXScratchToChunkSIMD(LightGridChunk& chunk) { #define LIGHTING(k) lightingScratch[i][j][k] __m128i maskR = _mm_set1_epi32(0xff); __m128i maskG = _mm_set1_epi32(0xff00); __m128i maskB = _mm_set1_epi32(0xff0000); __m128i maskA = _mm_set1_epi32(0xff000000); for (int i = 1; i < kLightGridChunkSizeY + 1; ++i) for (int j = 1; j < kLightGridChunkSizeXZ + 1; ++j) { for (int k = 1; k < kLightGridChunkSizeXZ + 1; k += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { __m128i prev = _mm_loadu_si128((__m128i*)&LIGHTING(k-1)); __m128i cur = _mm_loadu_si128((__m128i*)&LIGHTING(k)); __m128i newCur = _mm_loadu_si128((__m128i*)&LIGHTING(k+1)); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: we had two steps before, and we're doing the 3rd blur step; the filter weights should be 1/27 // Due to fixed point restriction, we divided by 4 in first two steps, yielding a weight of 1/16 // Divide by two here to have a weight of 1/32, which is close enough to 1/27 __m128i value = _mm_srli_epi32(_mm_add_epi32(_mm_add_epi32(prev, cur), newCur), 1); // now we have to repack from 10 10 10 format to 8 8 8 __m128i chanR = _mm_and_si128(_mm_srli_epi32(value, 20), maskR); __m128i chanG = _mm_and_si128(_mm_srli_epi32(value, 2), maskG); __m128i chanB = _mm_and_si128(_mm_slli_epi32(value, 16), maskB); // lighting has arbitrary rgb, but chanX should have alpha as zero - should be enough to or values together if we mask lighting __m128i lighting = _mm_loadu_si128((__m128i*)chunk.lighting[i-1][j-1][k-1]); __m128i result = _mm_or_si128(_mm_or_si128(chanR, chanG), _mm_or_si128(chanB, _mm_and_si128(lighting, maskA))); _mm_storeu_si128((__m128i*)chunk.lighting[i-1][j-1][k-1], result); } } #undef LIGHTING } #endif #ifdef SIMD_NEON void LightGrid::lightingBlurAxisXScratchToChunkSIMD(LightGridChunk& chunk) { #define LIGHTING(k) lightingScratch[i][j][k] uint32x4_t maskR = vdupq_n_u32(0xff); uint32x4_t maskG = vdupq_n_u32(0xff00); uint32x4_t maskB = vdupq_n_u32(0xff0000); uint32x4_t maskA = vdupq_n_u32(0xff000000); for (int i = 1; i < kLightGridChunkSizeY + 1; ++i) for (int j = 1; j < kLightGridChunkSizeXZ + 1; ++j) { for (int k = 1; k < kLightGridChunkSizeXZ + 1; k += 4) // note: this loop processes a few extra values - it's ok since we pad X in lighting scratch by 4 pixels { uint32x4_t prev = vld1q_u32(&LIGHTING(k-1)); uint32x4_t cur = vld1q_u32(&LIGHTING(k)); uint32x4_t newCur = vld1q_u32(&LIGHTING(k+1)); // ff3fcff mask masks the value bits from packed representation, setting carry bits to zero // Note: we had two steps before, and we're doing the 3rd blur step; the filter weights should be 1/27 // Due to fixed point restriction, we divided by 4 in first two steps, yielding a weight of 1/16 // Divide by two here to have a weight of 1/32, which is close enough to 1/27 uint32x4_t value = vshrq_n_u32(vaddq_u32(vaddq_u32(prev, cur), newCur), 1); // now we have to repack from 10 10 10 format to 8 8 8 uint32x4_t chanR = vandq_u32(vshrq_n_u32(value, 20), maskR); uint32x4_t chanG = vandq_u32(vshrq_n_u32(value, 2), maskG); uint32x4_t chanB = vandq_u32(vshlq_n_u32(value, 16), maskB); // lighting has arbitrary rgb, but chanX should have alpha as zero - should be enough to or values together if we mask lighting uint32x4_t lighting = vreinterpretq_u32_u8(vld1q_u8(chunk.lighting[i-1][j-1][k-1])); uint32x4_t result = vorrq_u32(vorrq_u32(chanR, chanG), vorrq_u32(chanB, vandq_u32(lighting, maskA))); vst1q_u8(chunk.lighting[i-1][j-1][k-1], vreinterpretq_u8_u32(result)); } } #undef LIGHTING } #endif void LightGrid::lightingUpdateAverageImpl(LightGridChunk& chunk) { for (int y = 0; y < kLightGridChunkSizeY; ++y) { unsigned int global = 0; unsigned int skylight = 0; unsigned int weight = 0; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { int w = 255 - chunk.occupancy[y][z][x]; global += chunk.lighting[y][z][x][3] * w; skylight += chunk.lightingSkylight[y][z][x] * w; weight += w; } chunk.lightingAverageGlobal[y] = global; chunk.lightingAverageSkylight[y] = skylight; chunk.lightingAverageWeight[y] = weight; } } #ifdef SIMD_SSE2 void LightGrid::lightingUpdateAverageImplSIMD(LightGridChunk& chunk) { __m128i zero = _mm_setzero_si128(); __m128i b255 = _mm_set1_epi8((char)255); for (int y = 0; y < kLightGridChunkSizeY; ++y) { __m128i global4[4] = {zero, zero, zero, zero}; __m128i skylight4[4] = {zero, zero, zero, zero}; __m128i weight4[4] = {zero, zero, zero, zero}; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { __m128i occ = _mm_loadu_si128((__m128i*)&chunk.occupancy[y][z][x]); __m128i light0 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+0]); __m128i light1 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+4]); __m128i light2 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+8]); __m128i light3 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+12]); __m128i skylight = _mm_loadu_si128((__m128i*)&chunk.lightingSkylight[y][z][x]); __m128i skylight01 = _mm_unpacklo_epi8(skylight, zero); __m128i skylight23 = _mm_unpackhi_epi8(skylight, zero); __m128i skylight0 = _mm_unpacklo_epi8(skylight01, zero); __m128i skylight1 = _mm_unpackhi_epi8(skylight01, zero); __m128i skylight2 = _mm_unpacklo_epi8(skylight23, zero); __m128i skylight3 = _mm_unpackhi_epi8(skylight23, zero); // compute weights (255 - occupancy) into weight0...weight3 __m128i weight = _mm_sub_epi8(b255, occ); __m128i weight01 = _mm_unpacklo_epi8(weight, zero); __m128i weight23 = _mm_unpackhi_epi8(weight, zero); __m128i weight0 = _mm_unpacklo_epi8(weight01, zero); __m128i weight1 = _mm_unpackhi_epi8(weight01, zero); __m128i weight2 = _mm_unpacklo_epi8(weight23, zero); __m128i weight3 = _mm_unpackhi_epi8(weight23, zero); // multiply global lighting and skylight by weight (result fits in 16 bits) __m128i globalw0 = _mm_mullo_epi16(weight0, _mm_srli_epi32(light0, 24)); __m128i globalw1 = _mm_mullo_epi16(weight1, _mm_srli_epi32(light1, 24)); __m128i globalw2 = _mm_mullo_epi16(weight2, _mm_srli_epi32(light2, 24)); __m128i globalw3 = _mm_mullo_epi16(weight3, _mm_srli_epi32(light3, 24)); __m128i skylightw0 = _mm_mullo_epi16(weight0, skylight0); __m128i skylightw1 = _mm_mullo_epi16(weight1, skylight1); __m128i skylightw2 = _mm_mullo_epi16(weight2, skylight2); __m128i skylightw3 = _mm_mullo_epi16(weight3, skylight3); // accumulate global4[0] = _mm_add_epi32(global4[0], globalw0); global4[1] = _mm_add_epi32(global4[1], globalw1); global4[2] = _mm_add_epi32(global4[2], globalw2); global4[3] = _mm_add_epi32(global4[3], globalw3); skylight4[0] = _mm_add_epi32(skylight4[0], skylightw0); skylight4[1] = _mm_add_epi32(skylight4[1], skylightw1); skylight4[2] = _mm_add_epi32(skylight4[2], skylightw2); skylight4[3] = _mm_add_epi32(skylight4[3], skylightw3); weight4[0] = _mm_add_epi32(weight4[0], weight0); weight4[1] = _mm_add_epi32(weight4[1], weight1); weight4[2] = _mm_add_epi32(weight4[2], weight2); weight4[3] = _mm_add_epi32(weight4[3], weight3); } } // merge accumulated results __m128i global = _mm_add_epi32(_mm_add_epi32(global4[0], global4[1]), _mm_add_epi32(global4[2], global4[3])); __m128i skylight = _mm_add_epi32(_mm_add_epi32(skylight4[0], skylight4[1]), _mm_add_epi32(skylight4[2], skylight4[3])); __m128i weight = _mm_add_epi32(_mm_add_epi32(weight4[0], weight4[1]), _mm_add_epi32(weight4[2], weight4[3])); unsigned int globalBuf[4]; unsigned int skylightBuf[4]; unsigned int weightBuf[4]; _mm_storeu_si128((__m128i*)globalBuf, global); _mm_storeu_si128((__m128i*)skylightBuf, skylight); _mm_storeu_si128((__m128i*)weightBuf, weight); chunk.lightingAverageGlobal[y] = globalBuf[0] + globalBuf[1] + globalBuf[2] + globalBuf[3]; chunk.lightingAverageSkylight[y] = skylightBuf[0] + skylightBuf[1] + skylightBuf[2] + skylightBuf[3]; chunk.lightingAverageWeight[y] = weightBuf[0] + weightBuf[1] + weightBuf[2] + weightBuf[3]; } } #endif #ifdef SIMD_NEON void LightGrid::lightingUpdateAverageImplSIMD(LightGridChunk& chunk) { uint32x4_t zero = vdupq_n_u32(0); uint8x16_t b255 = vdupq_n_u8(255); for (int y = 0; y < kLightGridChunkSizeY; ++y) { uint32x4_t global4[4] = {zero, zero, zero, zero}; uint32x4_t skylight4[4] = {zero, zero, zero, zero}; uint32x4_t weight4[4] = {zero, zero, zero, zero}; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { uint8x16_t occ = vld1q_u8(&chunk.occupancy[y][z][x]); uint32x4_t light0 = vreinterpretq_u32_u8(vld1q_u8(chunk.lighting[y][z][x+0])); uint32x4_t light1 = vreinterpretq_u32_u8(vld1q_u8(chunk.lighting[y][z][x+4])); uint32x4_t light2 = vreinterpretq_u32_u8(vld1q_u8(chunk.lighting[y][z][x+8])); uint32x4_t light3 = vreinterpretq_u32_u8(vld1q_u8(chunk.lighting[y][z][x+12])); uint8x16_t skylight = vld1q_u8(&chunk.lightingSkylight[y][z][x]); uint16x8_t skylight01 = vmovl_u8(vget_low_u8(skylight)); uint16x8_t skylight23 = vmovl_u8(vget_high_u8(skylight)); uint32x4_t skylight0 = vmovl_u16(vget_low_u16(skylight01)); uint32x4_t skylight1 = vmovl_u16(vget_high_u16(skylight01)); uint32x4_t skylight2 = vmovl_u16(vget_low_u16(skylight23)); uint32x4_t skylight3 = vmovl_u16(vget_high_u16(skylight23)); // compute weights (255 - occupancy) into weight0...weight3 uint8x16_t weight = vsubq_u8(b255, occ); uint16x8_t weight01 = vmovl_u8(vget_low_u8(weight)); uint16x8_t weight23 = vmovl_u8(vget_high_u8(weight)); uint32x4_t weight0 = vmovl_u16(vget_low_u16(weight01)); uint32x4_t weight1 = vmovl_u16(vget_high_u16(weight01)); uint32x4_t weight2 = vmovl_u16(vget_low_u16(weight23)); uint32x4_t weight3 = vmovl_u16(vget_high_u16(weight23)); // accumulate global4[0] = vmlaq_u32(global4[0], weight0, vshrq_n_u32(light0, 24)); global4[1] = vmlaq_u32(global4[1], weight1, vshrq_n_u32(light1, 24)); global4[2] = vmlaq_u32(global4[2], weight2, vshrq_n_u32(light2, 24)); global4[3] = vmlaq_u32(global4[3], weight3, vshrq_n_u32(light3, 24)); skylight4[0] = vmlaq_u32(skylight4[0], weight0, skylight0); skylight4[1] = vmlaq_u32(skylight4[1], weight1, skylight1); skylight4[2] = vmlaq_u32(skylight4[2], weight2, skylight2); skylight4[3] = vmlaq_u32(skylight4[3], weight3, skylight3); weight4[0] = vaddq_u32(weight4[0], weight0); weight4[1] = vaddq_u32(weight4[1], weight1); weight4[2] = vaddq_u32(weight4[2], weight2); weight4[3] = vaddq_u32(weight4[3], weight3); } } // merge accumulated results uint32x4_t global = vaddq_u32(vaddq_u32(global4[0], global4[1]), vaddq_u32(global4[2], global4[3])); uint32x4_t skylight = vaddq_u32(vaddq_u32(skylight4[0], skylight4[1]), vaddq_u32(skylight4[2], skylight4[3])); uint32x4_t weight = vaddq_u32(vaddq_u32(weight4[0], weight4[1]), vaddq_u32(weight4[2], weight4[3])); unsigned int globalBuf[4]; unsigned int skylightBuf[4]; unsigned int weightBuf[4]; vst1q_u32(globalBuf, global); vst1q_u32(skylightBuf, skylight); vst1q_u32(weightBuf, weight); chunk.lightingAverageGlobal[y] = globalBuf[0] + globalBuf[1] + globalBuf[2] + globalBuf[3]; chunk.lightingAverageSkylight[y] = skylightBuf[0] + skylightBuf[1] + skylightBuf[2] + skylightBuf[3]; chunk.lightingAverageWeight[y] = weightBuf[0] + weightBuf[1] + weightBuf[2] + weightBuf[3]; } } #endif void LightGrid::lightingClearLocal(LightGridChunk& chunk) { for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; x += 4) { chunk.lighting[y][z][x+0][0] = 0; chunk.lighting[y][z][x+0][1] = 0; chunk.lighting[y][z][x+0][2] = 0; chunk.lighting[y][z][x+1][0] = 0; chunk.lighting[y][z][x+1][1] = 0; chunk.lighting[y][z][x+1][2] = 0; chunk.lighting[y][z][x+2][0] = 0; chunk.lighting[y][z][x+2][1] = 0; chunk.lighting[y][z][x+2][2] = 0; chunk.lighting[y][z][x+3][0] = 0; chunk.lighting[y][z][x+3][1] = 0; chunk.lighting[y][z][x+3][2] = 0; } } void LightGrid::lightingClearGlobal(LightGridChunk& chunk) { for (int y = 0; y < kLightGridChunkSizeY; ++y) for (int z = 0; z < kLightGridChunkSizeXZ; ++z) for (int x = 0; x < kLightGridChunkSizeXZ; x += 8) { chunk.lighting[y][z][x+0][3] = 255; chunk.lighting[y][z][x+1][3] = 255; chunk.lighting[y][z][x+2][3] = 255; chunk.lighting[y][z][x+3][3] = 255; chunk.lighting[y][z][x+4][3] = 255; chunk.lighting[y][z][x+5][3] = 255; chunk.lighting[y][z][x+6][3] = 255; chunk.lighting[y][z][x+7][3] = 255; } } void LightGrid::lightingComposit(const LightGridChunk& chunk, unsigned char* data, unsigned int rowPitch, unsigned int slicePitch) { RBXPROFILER_SCOPE("Render", "lightingComposit"); if (!lightShadows || (skyAmbient.r == 0 && skyAmbient.g == 0 && skyAmbient.b == 0)) { unsigned char* dataSlice = data; for (int y = 0; y < kLightGridChunkSizeY; ++y) { unsigned char* dataRow = dataSlice; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { memcpy(dataRow, chunk.lighting[y][z][0], kLightGridChunkSizeXZ * 4); dataRow += rowPitch; } dataSlice += slicePitch; } } else { Color3uint8 storageSkyAmbient = transformColor(skyAmbient); #if defined(__ANDROID__) && __GNUC__ == 4 && __GNUC_MINOR__ == 6 // GCC 4.6 miscompiles SIMD implementation in Release, causing lighting bugs lightingCompositImplSIMD(chunk, data, rowPitch, slicePitch, storageSkyAmbient); #else SIMDCALL(lightingCompositImpl, (chunk, data, rowPitch, slicePitch, storageSkyAmbient)); #endif } } void LightGrid::lightingCompositImpl(const LightGridChunk& chunk, unsigned char* data, unsigned int rowPitch, unsigned int slicePitch, const Color3uint8& skyAmbient) { unsigned char skyTable[256][4]; for (int i = 0; i < 256; ++i) { skyTable[i][0] = (i * skyAmbient.r + 255) >> 8; skyTable[i][1] = (i * skyAmbient.g + 255) >> 8; skyTable[i][2] = (i * skyAmbient.b + 255) >> 8; skyTable[i][3] = 0; } unsigned char* dataSlice = data; for (int y = 0; y < kLightGridChunkSizeY; ++y) { unsigned char* dataRow = dataSlice; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { for (int x = 0; x < kLightGridChunkSizeXZ; ++x) { unsigned char skylight = chunk.lightingSkylight[y][z][x]; unsigned int a = *reinterpret_cast(chunk.lighting[y][z][x]); unsigned int b = *reinterpret_cast(skyTable[skylight]); // lose low bits, parallel add (carry goes into high bit of each byte) unsigned int res7 = ((a >> 1) & 0x7f7f7f7f) + ((b >> 1) & 0x7f7f7f7f); // convert carry bits into 7-bit mask unsigned int carry = (res7 & 0x80808080); unsigned int carryMask = carry - (carry >> 7); // carry means overflow -> max255 unsigned int res = (res7 | carryMask) << 1; *reinterpret_cast(dataRow + x * 4) = res; } dataRow += rowPitch; } dataSlice += slicePitch; } } #ifdef SIMD_SSE2 void LightGrid::lightingCompositImplSIMD(const LightGridChunk& chunk, unsigned char* data, unsigned int rowPitch, unsigned int slicePitch, const Color3uint8& skyAmbient) { __m128i zero = _mm_setzero_si128(); __m128i round = _mm_set1_epi16(255); __m128i skyAmbientR = _mm_set1_epi16(skyAmbient.r); __m128i skyAmbientG = _mm_set1_epi16(skyAmbient.g); __m128i skyAmbientB = _mm_set1_epi16(skyAmbient.b); __m128i maskHighByte = _mm_set1_epi16((short)0xff00); unsigned char* dataSlice = data; for (int y = 0; y < kLightGridChunkSizeY; ++y) { unsigned char* dataRow = dataSlice; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { __m128i skylight = _mm_loadu_si128((__m128i*)&chunk.lightingSkylight[y][z][x]); __m128i light0 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+0]); __m128i light1 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+4]); __m128i light2 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+8]); __m128i light3 = _mm_loadu_si128((__m128i*)chunk.lighting[y][z][x+12]); __m128i skylight01 = _mm_unpacklo_epi8(skylight, zero); __m128i skylight23 = _mm_unpackhi_epi8(skylight, zero); __m128i skyR01 = _mm_add_epi16(_mm_mullo_epi16(skylight01, skyAmbientR), round); __m128i skyR23 = _mm_add_epi16(_mm_mullo_epi16(skylight23, skyAmbientR), round); __m128i skyG01 = _mm_add_epi16(_mm_mullo_epi16(skylight01, skyAmbientG), round); __m128i skyG23 = _mm_add_epi16(_mm_mullo_epi16(skylight23, skyAmbientG), round); __m128i skyB01 = _mm_add_epi16(_mm_mullo_epi16(skylight01, skyAmbientB), round); __m128i skyB23 = _mm_add_epi16(_mm_mullo_epi16(skylight23, skyAmbientB), round); // xform . r0 . r1 . r2 . r3 and . g0 . g1 . g2 . g3 to . . r0 g0 . . r1 g1 // shift right to get to r0 g0 . . r1 g1 . . (little endian) __m128i skyRG0 = _mm_srli_epi32(_mm_unpacklo_epi8(skyR01, skyG01), 16); __m128i skyRG1 = _mm_srli_epi32(_mm_unpackhi_epi8(skyR01, skyG01), 16); __m128i skyRG2 = _mm_srli_epi32(_mm_unpacklo_epi8(skyR23, skyG23), 16); __m128i skyRG3 = _mm_srli_epi32(_mm_unpackhi_epi8(skyR23, skyG23), 16); // xform . b0 . b1 . b2 . b3 to . . b0 . . . b1 . __m128i skyB01_masked = _mm_and_si128(skyB01, maskHighByte); __m128i skyB23_masked = _mm_and_si128(skyB23, maskHighByte); __m128i skyB0 = _mm_unpacklo_epi8(skyB01_masked, zero); __m128i skyB1 = _mm_unpackhi_epi8(skyB01_masked, zero); __m128i skyB2 = _mm_unpacklo_epi8(skyB23_masked, zero); __m128i skyB3 = _mm_unpackhi_epi8(skyB23_masked, zero); // add everything together __m128i result0 = _mm_adds_epu8(_mm_adds_epu8(skyRG0, light0), skyB0); __m128i result1 = _mm_adds_epu8(_mm_adds_epu8(skyRG1, light1), skyB1); __m128i result2 = _mm_adds_epu8(_mm_adds_epu8(skyRG2, light2), skyB2); __m128i result3 = _mm_adds_epu8(_mm_adds_epu8(skyRG3, light3), skyB3); // store! __m128i* dest = (__m128i*)(dataRow + x * 4); _mm_storeu_si128(dest + 0, result0); _mm_storeu_si128(dest + 1, result1); _mm_storeu_si128(dest + 2, result2); _mm_storeu_si128(dest + 3, result3); } dataRow += rowPitch; } dataSlice += slicePitch; } } #endif #ifdef SIMD_NEON void LightGrid::lightingCompositImplSIMD(const LightGridChunk& chunk, unsigned char* data, unsigned int rowPitch, unsigned int slicePitch, const Color3uint8& skyAmbient) { uint16x8_t round = vdupq_n_u16(255); uint8x8_t skyAmbientR = vdup_n_u8(skyAmbient.r); uint8x8_t skyAmbientG = vdup_n_u8(skyAmbient.g); uint8x8_t skyAmbientB = vdup_n_u8(skyAmbient.b); unsigned char* dataSlice = data; for (int y = 0; y < kLightGridChunkSizeY; ++y) { unsigned char* dataRow = dataSlice; for (int z = 0; z < kLightGridChunkSizeXZ; ++z) { for (int x = 0; x < kLightGridChunkSizeXZ; x += 16) { uint8x16_t skylight = vld1q_u8(&chunk.lightingSkylight[y][z][x]); uint8x16x4_t light = vld4q_u8(chunk.lighting[y][z][x]); uint8x8_t skylight01 = vget_low_u8(skylight); uint8x8_t skylight23 = vget_high_u8(skylight); uint16x8_t skyR01 = vmlal_u8(round, skylight01, skyAmbientR); uint16x8_t skyR23 = vmlal_u8(round, skylight23, skyAmbientR); uint16x8_t skyG01 = vmlal_u8(round, skylight01, skyAmbientG); uint16x8_t skyG23 = vmlal_u8(round, skylight23, skyAmbientG); uint16x8_t skyB01 = vmlal_u8(round, skylight01, skyAmbientB); uint16x8_t skyB23 = vmlal_u8(round, skylight23, skyAmbientB); uint8x16_t skyR = vuzpq_u8(vreinterpretq_u8_u16(skyR01), vreinterpretq_u8_u16(skyR23)).val[1]; uint8x16_t skyG = vuzpq_u8(vreinterpretq_u8_u16(skyG01), vreinterpretq_u8_u16(skyG23)).val[1]; uint8x16_t skyB = vuzpq_u8(vreinterpretq_u8_u16(skyB01), vreinterpretq_u8_u16(skyB23)).val[1]; uint8x16x4_t result; result.val[0] = vqaddq_u8(skyR, light.val[0]); result.val[1] = vqaddq_u8(skyG, light.val[1]); result.val[2] = vqaddq_u8(skyB, light.val[2]); result.val[3] = light.val[3]; // store! vst4q_u8(dataRow + x * 4, result); } dataRow += rowPitch; } dataSlice += slicePitch; } } #endif void LightGrid::lightingUploadChunk(LightGridChunk& chunk) { RBXPROFILER_SCOPE("Render", "lightingUploadChunk"); if (!texture) return; Vector3int32 wrappedIndex = getWrappedChunkIndex(chunk.index); Vector3int32 gridPos = wrappedIndex * kLightGridChunkSize; if (texture->getType() == Texture::Type_3D) { TextureRegion region(gridPos.x, gridPos.z, gridPos.y, kLightGridChunkSizeXZ, kLightGridChunkSizeXZ, kLightGridChunkSizeY); if (texture->supportsLocking()) { Texture::LockResult locked = texture->lock(0, 0, region); lightingComposit(chunk, static_cast(locked.data), locked.rowPitch, locked.slicePitch); texture->unlock(0, 0); } else { lightingComposit(chunk, uploadScratch[0][0][0], kLightGridChunkSizeXZ * 4, kLightGridChunkSizeXZ * kLightGridChunkSizeXZ * 4); texture->upload(0, 0, region, uploadScratch, sizeof(uploadScratch)); } } else { // Assume a square total depth of the texture, with sqrt x sqrt tile packing int gridDepth = chunkCount.y * kLightGridChunkSizeY; int gridDepthSqrt = isqrt(gridDepth); int gridWidth = chunkCount.x * kLightGridChunkSizeXZ; int gridHeight = chunkCount.z * kLightGridChunkSizeXZ; lightingComposit(chunk, uploadScratch[0][0][0], kLightGridChunkSizeXZ * 4, kLightGridChunkSizeXZ * kLightGridChunkSizeXZ * 4); // Fill border row/columns with a border color fillLightingBorders(uploadScratch, chunk.index, cornerChunkIndex, chunkCount, transformColor(skyAmbient)); for (int y = 0; y < kLightGridChunkSizeY; ++y) { int slice = (gridPos.y + y) % gridDepth; int sliceOffsetX = (slice % gridDepthSqrt) * gridWidth; int sliceOffsetY = (slice / gridDepthSqrt) * gridHeight; // We need to wrap-around here - chunk can't cross texture boundary, but chunks from the same slice can wrap around the boundary int boxLeft = (gridPos.x + sliceOffsetX) % texture->getWidth(); int boxTop = (gridPos.z + sliceOffsetY) % texture->getHeight(); TextureRegion sliceRegion(boxLeft, boxTop, kLightGridChunkSizeXZ, kLightGridChunkSizeXZ); texture->upload(0, 0, sliceRegion, uploadScratch[y], sizeof(uploadScratch[y])); } } } void LightGrid::lightingUploadCommit() { RBXPROFILER_SCOPE("Render", "lightingUploadCommit"); if (texture) texture->commitChanges(); } void LightGrid::lightingClearAll() { lightShadows = false; skyAmbient = Color3uint8(); lightBorderGlobal = 255; lightBorderSkylight = 255; for (size_t i = 0; i < chunks.size(); ++i) { LightGridChunk& chunk = *chunks[i]; chunk.distancePriority = 0; chunk.dirty = 0; chunk.neverDirty = 0; chunk.age = 0; fillDummyLighting(chunk, lightBorderGlobal, lightBorderSkylight); } } void LightGrid::lightingUploadAll() { if (!texture) return; if (texture->getType() == Texture::Type_3D && texture->supportsLocking()) { // This is much faster than the per-chunk upload method TextureRegion region(0, 0, 0, texture->getWidth(), texture->getHeight(), texture->getDepth()); Texture::LockResult locked = texture->lock(0, 0, region); for (size_t i = 0; i < chunks.size(); ++i) { LightGridChunk& chunk = *chunks[i]; Vector3int32 wrappedIndex = ((chunk.index % chunkCount) + chunkCount) % chunkCount; Vector3int32 gridPos = wrappedIndex * kLightGridChunkSize; RBXASSERT(locked.rowPitch >= kLightGridChunkSizeXZ * 4); unsigned char* dataSlice = static_cast(locked.data) + locked.slicePitch * gridPos.y + locked.rowPitch * gridPos.z + gridPos.x * 4; lightingComposit(chunk, dataSlice, locked.rowPitch, locked.slicePitch); } texture->unlock(0, 0); } else { for (size_t i = 0; i < chunks.size(); ++i) lightingUploadChunk(*chunks[i]); } } void LightGrid::relocateGrid(const Vector3int32& newCorner, bool skipChunkUpdate) { RBXPROFILER_SCOPE("Render", "relocateGrid"); // Gather all chunks and clear out pointers to them (we'll fill them back) std::vector oldChunks = chunks; for (size_t i = 0; i < chunks.size(); ++i) chunks[i] = NULL; // Put all old chunks that are in the new grid to their old locations for (size_t i = 0; i < oldChunks.size(); ) { Vector3int32 localIndex = oldChunks[i]->index - newCorner; if (isChunkInsideGridLocal(localIndex)) { unsigned int offset = getArrayOffset(localIndex, chunkCount); RBXASSERT(chunks[offset] == NULL); chunks[offset] = oldChunks[i]; // Fast remove from array oldChunks[i] = oldChunks.back(); oldChunks.pop_back(); } else { i++; } } std::vector newChunks; // Now fill all remaining chunks with old chunks for (int cy = 0; cy < chunkCount.y; ++cy) for (int cz = 0; cz < chunkCount.z; ++cz) for (int cx = 0; cx < chunkCount.x; ++cx) { unsigned int offset = getArrayOffset(Vector3int32(cx, cy, cz), chunkCount); if (!chunks[offset]) { RBXASSERT(!oldChunks.empty()); LightGridChunk* chunk = oldChunks.back(); oldChunks.pop_back(); chunks[offset] = chunk; chunk->index = newCorner + Vector3int32(cx, cy, cz); chunk->dirty = LightGridChunk::Dirty_Occupancy | LightGridChunk::Dirty_LightingGlobal | LightGridChunk::Dirty_LightingLocal | LightGridChunk::Dirty_LightingSkylight; chunk->neverDirty = 0; newChunks.push_back(chunk); } } RBXASSERT(oldChunks.empty()); // Update grid corner index cornerChunkIndex = newCorner; if (!skipChunkUpdate) { // Fill new chunks with dummy data for (size_t i = 0; i < newChunks.size(); ++i) fillDummyLighting(*newChunks[i], lightBorderGlobal, lightBorderSkylight); if (texture && texture->getType() == Texture::Type_2D) { // Upload the entire grid since we bake border color in the texture lightingUploadAll(); } else { // Upload new chunks so that we don't see stale data after relocation for (size_t i = 0; i < newChunks.size(); ++i) lightingUploadChunk(*newChunks[i]); } lightingUploadCommit(); } } Color3uint8 LightGrid::transformColor(const Color3uint8& color) const { return (visualEngine->getDevice()->getCaps().colorOrderBGR) ? color.bgr() : color; } Color4uint8 LightGrid::transformColor(const Color4uint8& color) const { return Color4uint8(transformColor(color.rgb()), color.a); } Vector3int32 LightGrid::getWrappedChunkIndex(const Vector3int32& index) const { if (texture && texture->getType() == Texture::Type_2D) return index - cornerChunkIndex; else return ((index % chunkCount) + chunkCount) % chunkCount; } void LightGrid::onDeviceRestored() { FASTLOG(FLog::RenderLightGrid, "LightGrid: Device restored, reuploading the texture"); lightingUploadAll(); } } }