mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
3778 lines
161 KiB
C++
3778 lines
161 KiB
C++
#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 <arm_neon.h>
|
|
#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<kLightGridChunkSizeXZ>::value);
|
|
BOOST_STATIC_ASSERT(kLightGridChunkSizeY == 1 << boost::static_log2<kLightGridChunkSizeY>::value);
|
|
|
|
// Use right shift instead of division since right shift rounds down instead of truncating
|
|
int cx = pos.x >> boost::static_log2<kLightGridChunkSizeXZ>::value;
|
|
int cy = pos.y >> boost::static_log2<kLightGridChunkSizeY>::value;
|
|
int cz = pos.z >> boost::static_log2<kLightGridChunkSizeXZ>::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<LightObject*>& 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<unsigned int>(index.x) < static_cast<unsigned int>(chunkCount.x) &&
|
|
static_cast<unsigned int>(index.y) < static_cast<unsigned int>(chunkCount.y) &&
|
|
static_cast<unsigned int>(index.z) < static_cast<unsigned int>(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> texture;
|
|
shared_ptr<Texture> 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<float> 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<float>(sliceOffsetX) / gridDepthSqrt);
|
|
lutData.push_back(static_cast<float>(sliceOffsetY) / gridDepthSqrt);
|
|
lutData.push_back(static_cast<float>(sliceNextOffsetX) / gridDepthSqrt);
|
|
lutData.push_back(static_cast<float>(sliceNextOffsetY) / gridDepthSqrt);
|
|
}
|
|
|
|
// Upload LUT to a texture
|
|
if (visualEngine->getDevice()->getCaps().supportsTextureHalfFloat)
|
|
{
|
|
std::vector<unsigned short> 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<unsigned char> 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<Texture>(), shared_ptr<Texture>(), chunkCount);
|
|
}
|
|
}
|
|
|
|
LightGrid::LightGrid(VisualEngine* visualEngine, const shared_ptr<Texture>& texture, const shared_ptr<Texture>& 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<LightObject*> 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, <true>());
|
|
SIMDCALL(lightingBlurAxisYZScratch, <false>());
|
|
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<true, true>(chunk, lightContrib);
|
|
else
|
|
lightingUpdateDirectionalImpl<true, false>(chunk, lightContrib);
|
|
}
|
|
else
|
|
{
|
|
if (negZ)
|
|
lightingUpdateDirectionalImpl<false, true>(chunk, lightContrib);
|
|
else
|
|
lightingUpdateDirectionalImpl<false, false>(chunk, lightContrib);
|
|
}
|
|
}
|
|
|
|
template <bool NegX, bool NegZ> 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<LightObject*>& lights, const Extents& chunkExtents, SpatialHashedScene* spatialHashedScene)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "lightingGetLights");
|
|
|
|
std::vector<CullableSceneNode*> nodes;
|
|
spatialHashedScene->queryExtents(nodes, chunkExtents, CullableSceneNode::Flags_LightObject);
|
|
|
|
for (CullableSceneNode* node: nodes)
|
|
{
|
|
LightObject* lobj = static_cast<LightObject*>(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, <true>(extentsMin, extentsMax, position, range, color, intensity));
|
|
}
|
|
else
|
|
{
|
|
SIMDCALL(lightingUpdatePointLightScratch, <false>(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, <true>(extentsMin, extentsMax, position, range, lobj->getDirection(), lobj->getAngle(), color, intensity));
|
|
}
|
|
else
|
|
{
|
|
SIMDCALL(lightingUpdateSpotLightScratch, <false>(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, <false>(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 <int Axis> 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 <int Axis> 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<true>(chunk, extentsMin, Vector3int32(std::min(extentsMax.x, lightCell.x), extentsMax.y, extentsMax.z), lightCell, lightFraction);
|
|
}
|
|
|
|
if (lightCell.x < extentsMax.x)
|
|
{
|
|
lightingComputeShadowMaskYZ<false>(chunk, Vector3int32(std::max(extentsMin.x, lightCell.x + 1), extentsMin.y, extentsMin.z), extentsMax, lightCell, lightFraction);
|
|
}
|
|
}
|
|
|
|
template <bool NegX> void LightGrid::lightingComputeShadowMaskYZ(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction)
|
|
{
|
|
if (lightCell.y >= extentsMin.y)
|
|
{
|
|
lightingComputeShadowMaskZ<NegX, true>(chunk, extentsMin, Vector3int32(extentsMax.x, std::min(extentsMax.y, lightCell.y), extentsMax.z), lightCell, lightFraction);
|
|
}
|
|
|
|
if (lightCell.y < extentsMax.y)
|
|
{
|
|
lightingComputeShadowMaskZ<NegX, false>(chunk, Vector3int32(extentsMin.x, std::max(extentsMin.y, lightCell.y + 1), extentsMin.z), extentsMax, lightCell, lightFraction);
|
|
}
|
|
}
|
|
|
|
template <bool NegX, bool NegY> void LightGrid::lightingComputeShadowMaskZ(const LightGridChunk& chunk, const Vector3int32& extentsMin, const Vector3int32& extentsMax, const Vector3int32& lightCell, const Vector3& lightFraction)
|
|
{
|
|
if (lightCell.z >= extentsMin.z)
|
|
{
|
|
lightingComputeShadowMask<NegX, NegY, true>(chunk, extentsMin, Vector3int32(extentsMax.x, extentsMax.y, std::min(extentsMax.z, lightCell.z)), lightCell, lightFraction);
|
|
}
|
|
|
|
if (lightCell.z < extentsMax.z)
|
|
{
|
|
lightingComputeShadowMask<NegX, NegY, false>(chunk, Vector3int32(extentsMin.x, extentsMin.y, std::max(extentsMin.z, lightCell.z + 1)), extentsMax, lightCell, lightFraction);
|
|
}
|
|
}
|
|
|
|
template <bool NegX, bool NegY, bool NegZ> 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<NegX, NegY, NegZ>(chunk, extentsMin, extentsMax, lightCell, lightFraction);
|
|
else
|
|
#endif
|
|
lightingComputeShadowMaskImplLUT<NegX, NegY, NegZ>(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 <bool NegX, bool NegY, bool NegZ> 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 <bool NegX, bool NegY, bool NegZ> 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 <bool NegX, bool NegY, bool NegZ> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool Shadows> 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 <bool AxisY> 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 <bool AxisY> 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 <bool AxisY> 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<const unsigned int*>(chunk.lighting[y][z][x]);
|
|
unsigned int b = *reinterpret_cast<unsigned int*>(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<unsigned int*>(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<unsigned char*>(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<unsigned char*>(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<LightGridChunk*> 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<LightGridChunk*> 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();
|
|
}
|
|
|
|
}
|
|
}
|