diff options
31 files changed, 287 insertions, 99 deletions
diff --git a/assets/opensb/rendering/opengl20.config b/assets/opensb/rendering/opengl20.config index 923578c..70ff6c7 100644 --- a/assets/opensb/rendering/opengl20.config +++ b/assets/opensb/rendering/opengl20.config @@ -28,6 +28,12 @@ "textureSizeUniform" : "lightMapSize", "textureAddressing" : "clamp", "textureFiltering" : "linear" + }, + "tileLightMap" : { + "textureUniform" : "tileLightMap", + "textureSizeUniform" : "tileLightMapSize", + "textureAddressing" : "clamp", + "textureFiltering" : "linear" } }, diff --git a/assets/opensb/rendering/opengl20.frag b/assets/opensb/rendering/opengl20.frag index 92a121f..a57c029 100644 --- a/assets/opensb/rendering/opengl20.frag +++ b/assets/opensb/rendering/opengl20.frag @@ -6,7 +6,9 @@ uniform sampler2D texture2; uniform sampler2D texture3; uniform bool lightMapEnabled; uniform vec2 lightMapSize; +uniform vec2 tileLightMapSize; uniform sampler2D lightMap; +uniform sampler2D tileLightMap; uniform float lightMapMultiplier; varying vec2 fragmentTextureCoordinate; @@ -53,6 +55,12 @@ vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) { mix(sample1, sample0, sx), sy); } +vec4 sampleLightMap(vec2 texcoord, vec2 texscale) { + vec4 a = bicubicSample(lightMap, texcoord, texscale); + vec4 b = bicubicSample(tileLightMap, texcoord, texscale); + return mix(a, b, b.z); +} + void main() { vec4 texColor; if (fragmentTextureIndex > 2.9) { @@ -70,6 +78,6 @@ void main() { vec4 finalColor = texColor * fragmentColor; float finalLightMapMultiplier = fragmentLightMapMultiplier * lightMapMultiplier; if (lightMapEnabled && finalLightMapMultiplier > 0.0) - finalColor.rgb *= bicubicSample(lightMap, fragmentLightMapCoordinate, 1.0 / lightMapSize).rgb * finalLightMapMultiplier; + finalColor.rgb *= sampleLightMap(fragmentLightMapCoordinate, 1.0 / lightMapSize).rgb * finalLightMapMultiplier; gl_FragColor = finalColor; }
\ No newline at end of file diff --git a/source/base/StarCellularLighting.cpp b/source/base/StarCellularLighting.cpp index c29e78e..0578961 100644 --- a/source/base/StarCellularLighting.cpp +++ b/source/base/StarCellularLighting.cpp @@ -94,6 +94,39 @@ void CellularLightingCalculator::calculate(Image& output) { } } +void CellularLightingCalculator::setupImage(Image& image, PixelFormat format) const { + Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min()); + Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min()); + + image.reset(arrayMax[0] - arrayMin[0], arrayMax[1] - arrayMin[1], format); +} + +ThreadFunction<Image> CellularLightingCalculator::calculateAsync() { + return ThreadFunction<Image>([this]() { + Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min()); + Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min()); + + if (m_monochrome) + m_lightArray.right().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]); + else + m_lightArray.left().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]); + + Image output; + setupImage(output, PixelFormat::RGB24); + + for (size_t x = arrayMin[0]; x < arrayMax[0]; ++x) { + for (size_t y = arrayMin[1]; y < arrayMax[1]; ++y) { + if (m_monochrome) + output.set24(x - arrayMin[0], y - arrayMin[1], Color::grayf(m_lightArray.right().getLight(x, y)).toRgb()); + else + output.set24(x - arrayMin[0], y - arrayMin[1], Color::v3fToByte(m_lightArray.left().getLight(x, y))); + } + } + + return output; + }, "CellularLightingCalculator Thread"); +} + void CellularLightIntensityCalculator::setParameters(Json const& config) { m_lightArray.setParameters( config.getInt("spreadPasses"), diff --git a/source/base/StarCellularLighting.hpp b/source/base/StarCellularLighting.hpp index 7579a63..54be414 100644 --- a/source/base/StarCellularLighting.hpp +++ b/source/base/StarCellularLighting.hpp @@ -8,6 +8,7 @@ #include "StarColor.hpp" #include "StarInterpolation.hpp" #include "StarCellularLightArray.hpp" +#include "StarThread.hpp" namespace Star { @@ -44,6 +45,9 @@ public: // the call to 'begin', and formatted as RGB24. void calculate(Image& output); + void setupImage(Image& image, PixelFormat format = PixelFormat::RGB24) const; + + ThreadFunction<Image> calculateAsync(); private: Json m_config; bool m_monochrome; diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index a76a527..be32e58 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -361,18 +361,41 @@ void ClientApplication::render() { m_cinematicOverlay->render(); } else if (m_state > MainAppState::Title) { - if (auto worldClient = m_universeClient->worldClient()) { - if (auto renderer = Application::renderer()) + WorldClientPtr worldClient = m_universeClient->worldClient(); + RendererPtr renderer = Application::renderer(); + if (worldClient) { + if (renderer) renderer->setEffectParameter("lightMapEnabled", true); worldClient->render(m_renderData, TilePainter::BorderTileSize); - m_worldPainter->render(m_renderData); + // Might have to move lightmap adjustment code back into worldPainter->render + // eventually, can't be bothered to remove the passed lambda yet + m_worldPainter->render(m_renderData, [&]() { worldClient->waitForLighting(); }); m_mainInterface->renderInWorldElements(); - if (auto renderer = Application::renderer()) + if (renderer) renderer->setEffectParameter("lightMapEnabled", false); } m_mainInterface->render(); m_cinematicOverlay->render(); + if (worldClient && renderer) { + worldClient->waitForLighting(); + + if (m_renderData.isFullbright) { + renderer->setEffectTexture("lightMap", Image::filled(Vec2U(1, 1), { 255, 255, 255, 255 }, PixelFormat::RGB24)); + renderer->setEffectTexture("tileLightMap", Image::filled(Vec2U(1, 1), { 0, 0, 0, 0 }, PixelFormat::RGBA32)); + renderer->setEffectParameter("lightMapMultiplier", 1.0f); + } + else { + m_worldPainter->adjustLighting(m_renderData); + + WorldCamera const& camera = m_worldPainter->camera(); + renderer->setEffectParameter("lightMapMultiplier", assets->json("/rendering.config:lightMapMultiplier").toFloat()); + renderer->setEffectParameter("lightMapScale", Vec2F::filled(TilePixels * camera.pixelRatio())); + renderer->setEffectParameter("lightMapOffset", camera.worldToScreen(Vec2F(m_renderData.lightMinPosition))); + renderer->setEffectTexture("lightMap", m_renderData.lightMap); + renderer->setEffectTexture("tileLightMap", m_renderData.tileLightMap); + } + } } if (!m_errorScreen->accepted()) diff --git a/source/frontend/StarClientCommandProcessor.cpp b/source/frontend/StarClientCommandProcessor.cpp index f006d8e..b6ab53c 100644 --- a/source/frontend/StarClientCommandProcessor.cpp +++ b/source/frontend/StarClientCommandProcessor.cpp @@ -24,6 +24,7 @@ ClientCommandProcessor::ClientCommandProcessor(UniverseClientPtr universeClient, {"debug", bind(&ClientCommandProcessor::debug, this)}, {"boxes", bind(&ClientCommandProcessor::boxes, this)}, {"fullbright", bind(&ClientCommandProcessor::fullbright, this)}, + {"asyncLighting", bind(&ClientCommandProcessor::asyncLighting, this)}, {"setGravity", bind(&ClientCommandProcessor::setGravity, this, _1)}, {"resetGravity", bind(&ClientCommandProcessor::resetGravity, this)}, {"fixedCamera", bind(&ClientCommandProcessor::fixedCamera, this)}, @@ -151,6 +152,12 @@ String ClientCommandProcessor::fullbright() { ? "enabled" : "disabled"); } +String ClientCommandProcessor::asyncLighting() { + return strf("Asynchronous render lighting {}", + m_universeClient->worldClient()->toggleAsyncLighting() + ? "enabled" : "disabled"); +} + String ClientCommandProcessor::setGravity(StringList const& arguments) { if (!adminCommandAllowed()) return "You must be an admin to use this command."; diff --git a/source/frontend/StarClientCommandProcessor.hpp b/source/frontend/StarClientCommandProcessor.hpp index 9b0219b..3093450 100644 --- a/source/frontend/StarClientCommandProcessor.hpp +++ b/source/frontend/StarClientCommandProcessor.hpp @@ -31,6 +31,7 @@ private: String debug(); String boxes(); String fullbright(); + String asyncLighting(); String setGravity(StringList const& arguments); String resetGravity(); String fixedCamera(); diff --git a/source/game/StarEntityMap.cpp b/source/game/StarEntityMap.cpp index a3844dc..66d09a5 100644 --- a/source/game/StarEntityMap.cpp +++ b/source/game/StarEntityMap.cpp @@ -189,6 +189,7 @@ void EntityMap::forAllEntities(EntityCallback const& callback, function<bool(Ent // Even if there is no sort order, we still copy pointers to a temporary // list, so that it is safe to call addEntity from the callback. List<EntityPtr const*> allEntities; + allEntities.reserve(m_spatialMap.size()); for (auto const& entry : m_spatialMap.entries()) allEntities.append(&entry.second.value); diff --git a/source/game/StarMonster.cpp b/source/game/StarMonster.cpp index e9d78f8..b91607a 100644 --- a/source/game/StarMonster.cpp +++ b/source/game/StarMonster.cpp @@ -494,10 +494,7 @@ void Monster::render(RenderCallback* renderCallback) { renderCallback->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); renderCallback->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles()); - renderCallback->addLightSources(m_networkedAnimator.lightSources(position())); - renderCallback->addDrawables(m_statusController->drawables(), m_monsterVariant.renderLayer); - renderCallback->addLightSources(m_statusController->lightSources()); renderCallback->addParticles(m_statusController->pullNewParticles()); renderCallback->addAudios(m_statusController->pullNewAudios()); @@ -505,11 +502,16 @@ void Monster::render(RenderCallback* renderCallback) { for (auto drawablePair : m_scriptedAnimator.drawables()) renderCallback->addDrawable(drawablePair.first, drawablePair.second.value(m_monsterVariant.renderLayer)); - renderCallback->addLightSources(m_scriptedAnimator.lightSources()); renderCallback->addAudios(m_scriptedAnimator.pullNewAudios()); renderCallback->addParticles(m_scriptedAnimator.pullNewParticles()); } +void Monster::renderLightSources(RenderCallback* renderCallback) { + renderCallback->addLightSources(m_networkedAnimator.lightSources(position())); + renderCallback->addLightSources(m_statusController->lightSources()); + renderCallback->addLightSources(m_scriptedAnimator.lightSources()); +} + void Monster::setPosition(Vec2F const& pos) { m_movementController->setPosition(pos); } diff --git a/source/game/StarMonster.hpp b/source/game/StarMonster.hpp index ea95521..06ca26e 100644 --- a/source/game/StarMonster.hpp +++ b/source/game/StarMonster.hpp @@ -92,6 +92,8 @@ public: void render(RenderCallback* renderCallback) override; + void renderLightSources(RenderCallback* renderCallback) override; + void setPosition(Vec2F const& pos); Maybe<Json> receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) override; diff --git a/source/game/StarNpc.cpp b/source/game/StarNpc.cpp index 75311a6..1d8a80b 100644 --- a/source/game/StarNpc.cpp +++ b/source/game/StarNpc.cpp @@ -464,7 +464,6 @@ void Npc::render(RenderCallback* renderCallback) { renderCallback->addAudios(m_statusController->pullNewAudios()); renderCallback->addParticles(m_npcVariant.splashConfig.doSplash(position(), m_movementController->velocity(), world())); - renderCallback->addLightSources(lightSources()); m_tools->render(renderCallback, inToolRange(), m_shifting.get(), renderLayer); @@ -473,6 +472,10 @@ void Npc::render(RenderCallback* renderCallback) { m_effectEmitter->render(renderCallback); } +void Npc::renderLightSources(RenderCallback* renderCallback) { + renderCallback->addLightSources(lightSources()); +} + void Npc::setPosition(Vec2F const& pos) { m_movementController->setPosition(pos); } diff --git a/source/game/StarNpc.hpp b/source/game/StarNpc.hpp index 193ffce..7c6c5a9 100644 --- a/source/game/StarNpc.hpp +++ b/source/game/StarNpc.hpp @@ -94,6 +94,8 @@ public: void render(RenderCallback* renderCallback) override; + void renderLightSources(RenderCallback* renderCallback) override; + void setPosition(Vec2F const& pos); float maxHealth() const override; diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index 1dfc89d..1b55ac0 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -394,7 +394,6 @@ void Object::update(uint64_t) { void Object::render(RenderCallback* renderCallback) { renderParticles(renderCallback); - renderLights(renderCallback); renderSounds(renderCallback); for (auto const& imageKeyPair : m_imageKeys) @@ -412,11 +411,15 @@ void Object::render(RenderCallback* renderCallback) { for (auto drawablePair : m_scriptedAnimator.drawables()) renderCallback->addDrawable(drawablePair.first, drawablePair.second.value(renderLayer())); - renderCallback->addLightSources(m_scriptedAnimator.lightSources()); renderCallback->addParticles(m_scriptedAnimator.pullNewParticles()); renderCallback->addAudios(m_scriptedAnimator.pullNewAudios()); } +void Object::renderLightSources(RenderCallback* renderCallback) { + renderLights(renderCallback); + renderCallback->addLightSources(m_scriptedAnimator.lightSources()); +} + bool Object::damageTiles(List<Vec2I> const&, Vec2F const&, TileDamage const& tileDamage) { if (m_unbreakable) return false; diff --git a/source/game/StarObject.hpp b/source/game/StarObject.hpp index de3f370..e45a612 100644 --- a/source/game/StarObject.hpp +++ b/source/game/StarObject.hpp @@ -66,6 +66,8 @@ public: virtual void render(RenderCallback* renderCallback) override; + virtual void renderLightSources(RenderCallback* renderCallback) override; + virtual bool checkBroken() override; virtual Vec2I tilePosition() const override; diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index 93c05ae..188afef 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -1085,7 +1085,6 @@ void Player::render(RenderCallback* renderCallback) { if (!isTeleporting()) renderCallback->addOverheadBars(bars(), position()); renderCallback->addParticles(particles()); - renderCallback->addLightSources(lightSources()); m_tools->render(renderCallback, inToolRange(), m_shifting, renderLayer); @@ -1096,6 +1095,10 @@ void Player::render(RenderCallback* renderCallback) { m_deployment->render(renderCallback, position()); } +void Player::renderLightSources(RenderCallback* renderCallback) { + renderCallback->addLightSources(lightSources()); +} + Json Player::getGenericProperty(String const& name, Json const& defaultValue) const { return m_genericProperties.value(name, defaultValue); } diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index 88582a2..aa6853f 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -184,6 +184,8 @@ public: void render(RenderCallback* renderCallback) override; + void renderLightSources(RenderCallback* renderCallback) override; + Json getGenericProperty(String const& name, Json const& defaultValue = Json()) const; void setGenericProperty(String const& name, Json const& value); diff --git a/source/game/StarPlayerDeployment.cpp b/source/game/StarPlayerDeployment.cpp index b84e177..4565074 100644 --- a/source/game/StarPlayerDeployment.cpp +++ b/source/game/StarPlayerDeployment.cpp @@ -89,7 +89,6 @@ void PlayerDeployment::render(RenderCallback* renderCallback, Vec2F const& posit drawablePair.first.translate(position); renderCallback->addDrawable(drawablePair.first, drawablePair.second.value(RenderLayerPlayer)); } - renderCallback->addLightSources(m_scriptComponent.lightSources()); renderCallback->addParticles(m_scriptComponent.pullNewParticles()); for (auto audio : m_scriptComponent.pullNewAudios()) { audio->setPosition(position); @@ -97,4 +96,8 @@ void PlayerDeployment::render(RenderCallback* renderCallback, Vec2F const& posit } } +void PlayerDeployment::renderLightSources(RenderCallback* renderCallback) { + renderCallback->addLightSources(m_scriptComponent.lightSources()); +} + } diff --git a/source/game/StarPlayerDeployment.hpp b/source/game/StarPlayerDeployment.hpp index a5c8e3b..e9c9462 100644 --- a/source/game/StarPlayerDeployment.hpp +++ b/source/game/StarPlayerDeployment.hpp @@ -32,6 +32,7 @@ public: void render(RenderCallback* renderCallback, Vec2F const& position); + void renderLightSources(RenderCallback* renderCallback); private: World* m_world; Json m_config; diff --git a/source/game/StarProjectile.cpp b/source/game/StarProjectile.cpp index 060c4e3..89f3766 100644 --- a/source/game/StarProjectile.cpp +++ b/source/game/StarProjectile.cpp @@ -368,8 +368,14 @@ void Projectile::render(RenderCallback* renderCallback) { drawable.fullbright = m_config->fullbright; drawable.translate(position()); renderCallback->addDrawable(move(drawable), m_config->renderLayer); +} - renderCallback->addLightSource({position(), m_config->lightColor, m_config->pointLight, 0.0f, 0.0f, 0.0f}); +void Projectile::renderLightSources(RenderCallback* renderCallback) { + for (auto renderable : m_pendingRenderables) { + if (renderable.is<LightSource>()) + renderCallback->addLightSource(renderable.get<LightSource>()); + } + renderCallback->addLightSource({ position(), m_config->lightColor, m_config->pointLight, 0.0f, 0.0f, 0.0f }); } Maybe<Json> Projectile::receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) { @@ -981,8 +987,6 @@ void Projectile::renderPendingRenderables(RenderCallback* renderCallback) { renderCallback->addAudio(renderable.get<AudioInstancePtr>()); else if (renderable.is<Particle>()) renderCallback->addParticle(renderable.get<Particle>()); - else if (renderable.is<LightSource>()) - renderCallback->addLightSource(renderable.get<LightSource>()); } m_pendingRenderables.clear(); } diff --git a/source/game/StarProjectile.hpp b/source/game/StarProjectile.hpp index 4c0bb18..7a36dfe 100644 --- a/source/game/StarProjectile.hpp +++ b/source/game/StarProjectile.hpp @@ -58,6 +58,7 @@ public: void update(uint64_t currentStep) override; void render(RenderCallback* renderCallback) override; + void renderLightSources(RenderCallback* renderCallback) override; Maybe<Json> receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) override; diff --git a/source/game/StarVehicle.cpp b/source/game/StarVehicle.cpp index 83fc9e2..e60e6e6 100644 --- a/source/game/StarVehicle.cpp +++ b/source/game/StarVehicle.cpp @@ -329,15 +329,18 @@ void Vehicle::render(RenderCallback* renderer) { renderer->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); renderer->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles()); - renderer->addLightSources(m_networkedAnimator.lightSources(position())); for (auto drawablePair : m_scriptedAnimator.drawables()) renderer->addDrawable(drawablePair.first, drawablePair.second.value(renderLayer(VehicleLayer::Front))); - renderer->addLightSources(m_scriptedAnimator.lightSources()); renderer->addAudios(m_scriptedAnimator.pullNewAudios()); renderer->addParticles(m_scriptedAnimator.pullNewParticles()); } +void Vehicle::renderLightSources(RenderCallback* renderer) { + renderer->addLightSources(m_networkedAnimator.lightSources(position())); + renderer->addLightSources(m_scriptedAnimator.lightSources()); +} + List<LightSource> Vehicle::lightSources() const { auto lightSources = m_networkedAnimator.lightSources(position()); return lightSources; diff --git a/source/game/StarVehicle.hpp b/source/game/StarVehicle.hpp index f1ac2f3..381e207 100644 --- a/source/game/StarVehicle.hpp +++ b/source/game/StarVehicle.hpp @@ -55,6 +55,8 @@ public: void render(RenderCallback* renderer) override; + void renderLightSources(RenderCallback* renderer) override; + List<LightSource> lightSources() const override; bool shouldDestroy() const override; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 3a3fe39..b0b2c1e 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -34,6 +34,7 @@ WorldClient::WorldClient(PlayerPtr mainPlayer) { m_currentStep = 0; m_currentServerStep = 0.0; m_fullBright = false; + m_asyncLighting = true; m_worldDimTimer = GameTimer(m_clientConfig.getFloat("worldDimTime")); m_worldDimTimer.setDone(); m_worldDimLevel = 0.0f; @@ -77,10 +78,20 @@ WorldClient::WorldClient(PlayerPtr mainPlayer) { m_altMusicTrack.setVolume(0, 0, 0); m_altMusicActive = false; + m_stopLightingThread = false; + m_lightingThread = Thread::invoke("WorldClient::lightingMain", mem_fn(&WorldClient::lightingMain), this); + m_renderData = nullptr; + clearWorld(); } WorldClient::~WorldClient() { + m_stopLightingThread = true; + { + MutexLocker locker(m_lightingMutex); + m_lightingCond.broadcast(); + } + clearWorld(); } @@ -344,6 +355,84 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { renderData.geometry = m_geometry; + ClientRenderCallback lightingRenderCallback; + m_entityMap->forAllEntities([&](EntityPtr const& entity) { + if (m_startupHiddenEntities.contains(entity->entityId())) + return; + + entity->renderLightSources(&lightingRenderCallback); + }); + + renderLightSources = move(lightingRenderCallback.lightSources); + + RectI window = m_clientState.window(); + RectI tileRange = window.padded(bufferTiles); + RectI lightRange = window.padded(1); + //Kae: Padded by one to fix light spread issues at the edges of the frame. + + renderData.tileMinPosition = tileRange.min(); + renderData.lightMinPosition = lightRange.min(); + + Vec2U lightSize(lightRange.size()); + + renderData.tileLightMap.reset(lightSize, PixelFormat::RGBA32); + renderData.tileLightMap.fill(Vec4B::filled(0)); + + if (m_fullBright) { + renderData.lightMap.reset(lightSize, PixelFormat::RGB24); + renderData.lightMap.fill(Vec3B(255, 255, 255)); + } else { + m_lightingCalculator.begin(lightRange); + + Vec3F environmentLight = m_sky->environmentLight().toRgbF(); + float undergroundLevel = m_worldTemplate->undergroundLevel(); + auto liquidsDatabase = Root::singleton().liquidsDatabase(); + auto materialDatabase = Root::singleton().materialDatabase(); + + // Each column in tileEvalColumns is guaranteed to be no larger than the sector size. + m_tileArray->tileEvalColumns(m_lightingCalculator.calculationRegion(), [&](Vec2I const& pos, ClientTile const* column, size_t ySize) { + size_t baseIndex = m_lightingCalculator.baseIndexFor(pos); + for (size_t y = 0; y < ySize; ++y) { + auto& tile = column[y]; + + Vec3F light; + if (tile.foreground != EmptyMaterialId || tile.foregroundMod != NoModId) + light += materialDatabase->radiantLight(tile.foreground, tile.foregroundMod); + + if (tile.liquid.liquid != EmptyLiquidId && tile.liquid.level != 0.0f) + light += liquidsDatabase->radiantLight(tile.liquid); + if (tile.foregroundLightTransparent) { + if (tile.background != EmptyMaterialId || tile.backgroundMod != NoModId) + light += materialDatabase->radiantLight(tile.background, tile.backgroundMod); + if (tile.backgroundLightTransparent && pos[1] + y > undergroundLevel) + light += environmentLight; + } + m_lightingCalculator.setCellIndex(baseIndex + y, move(light), !tile.foregroundLightTransparent); + } + }); + + for (auto const& light : renderLightSources) { + Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), light.position); + if (light.pointLight) + m_lightingCalculator.addPointLight(position, Color::v3bToFloat(light.color), light.pointBeam, light.beamAngle, light.beamAmbience); + else + m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(light.color)); + } + + for (auto const& lightPair : m_particles->lightSources()) { + Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), lightPair.first); + m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(lightPair.second)); + } + + if (m_asyncLighting) { + m_renderData = &renderData; + m_lightingCond.signal(); + } + else { + m_lightingCalculator.calculate(renderData.lightMap); + } + } + float pulseAmount = Root::singleton().assets()->json("/highlights.config:interactivePulseAmount").toFloat(); float pulseRate = Root::singleton().assets()->json("/highlights.config:interactivePulseRate").toFloat(); float pulseLevel = 1 - pulseAmount * 0.5 * (sin(2 * Constants::pi * pulseRate * Time::monotonicMilliseconds() / 1000.0) + 1); @@ -368,6 +457,7 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { return; ClientRenderCallback renderCallback; + entity->render(&renderCallback); EntityDrawables ed; @@ -397,8 +487,6 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { } renderData.entityDrawables.append(move(ed)); - renderLightSources.appendAll(move(renderCallback.lightSources)); - if (directives) { int directiveIndex = unsigned(entity->entityId()) % directives->size(); for (auto& p : renderCallback.particles) @@ -414,14 +502,6 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { return a->entityId() < b->entityId(); }); - RectI window = m_clientState.window(); - RectI tileRange = window.padded(bufferTiles); - RectI lightRange = window.padded(1); - //Kae: Padded by one to fix light spread issues at the edges of the frame. - - renderData.tileMinPosition = tileRange.min(); - renderData.lightMinPosition = lightRange.min(); - m_tileArray->tileEachTo(renderData.tiles, tileRange, [](RenderTile& renderTile, Vec2I const&, ClientTile const& clientTile) { renderTile.foreground = clientTile.foreground; renderTile.foregroundMod = clientTile.foregroundMod; @@ -445,57 +525,6 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { renderTile.liquidLevel = floatToByte(clientTile.liquid.level); }); - Vec2U lightSize(lightRange.size()); - - if (m_fullBright) { - renderData.lightMap.reset(lightSize, PixelFormat::RGB24); - renderData.lightMap.fill(Vec3B(255, 255, 255)); - } else { - m_lightingCalculator.begin(lightRange); - - Vec3F environmentLight = m_sky->environmentLight().toRgbF(); - float undergroundLevel = m_worldTemplate->undergroundLevel(); - auto liquidsDatabase = Root::singleton().liquidsDatabase(); - auto materialDatabase = Root::singleton().materialDatabase(); - - // Each column in tileEvalColumns is guaranteed to be no larger than the sector size. - m_tileArray->tileEvalColumns(m_lightingCalculator.calculationRegion(), [&](Vec2I const& pos, ClientTile const* column, size_t ySize) { - size_t baseIndex = m_lightingCalculator.baseIndexFor(pos); - for (size_t y = 0; y < ySize; ++y) { - auto& tile = column[y]; - - Vec3F light; - if (tile.foreground != EmptyMaterialId || tile.foregroundMod != NoModId) - light += materialDatabase->radiantLight(tile.foreground, tile.foregroundMod); - - if (tile.liquid.liquid != EmptyLiquidId && tile.liquid.level != 0.0f) - light += liquidsDatabase->radiantLight(tile.liquid); - if (tile.foregroundLightTransparent) { - if (tile.background != EmptyMaterialId || tile.backgroundMod != NoModId) - light += materialDatabase->radiantLight(tile.background, tile.backgroundMod); - if (tile.backgroundLightTransparent && pos[1] + y > undergroundLevel) - light += environmentLight; - } - m_lightingCalculator.setCellIndex(baseIndex + y, move(light), !tile.foregroundLightTransparent); - } - }); - - for (auto const& light : renderLightSources) { - Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), light.position); - if (light.pointLight) - m_lightingCalculator.addPointLight(position, Color::v3bToFloat(light.color), light.pointBeam, light.beamAngle, light.beamAmbience); - else - m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(light.color)); - } - - for (auto const& lightPair : m_particles->lightSources()) { - Vec2F position = m_geometry.nearestTo(Vec2F(m_lightingCalculator.calculationRegion().min()), lightPair.first); - m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(lightPair.second)); - } - - m_lightingCalculator.calculate(renderData.lightMap); - } - for (auto const& previewTile : previewTiles) { Vec2I tileArrayPos = m_geometry.diff(previewTile.position, renderData.tileMinPosition); if (tileArrayPos[0] >= 0 && tileArrayPos[0] < (int)renderData.tiles.size(0) && tileArrayPos[1] >= 0 && tileArrayPos[1] < (int)renderData.tiles.size(1)) { @@ -524,8 +553,8 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { if (previewTile.updateLight) { Vec2I lightArrayPos = m_geometry.diff(previewTile.position, renderData.lightMinPosition); - if (lightArrayPos[0] >= 0 && lightArrayPos[0] < (int)renderData.lightMap.width() && lightArrayPos[1] >= 0 && lightArrayPos[1] < (int)renderData.lightMap.height()) - renderData.lightMap.set(Vec2U(lightArrayPos), previewTile.light); + if (lightArrayPos[0] >= 0 && lightArrayPos[0] < (int)renderData.tileLightMap.width() && lightArrayPos[1] >= 0 && lightArrayPos[1] < (int)renderData.tileLightMap.height()) + renderData.tileLightMap.set(Vec2U(lightArrayPos), previewTile.light); } } @@ -634,6 +663,11 @@ bool WorldClient::toggleFullbright() { return m_fullBright; } +bool WorldClient::toggleAsyncLighting() { + m_asyncLighting = !m_asyncLighting; + return m_asyncLighting; +} + bool WorldClient::toggleCollisionDebug() { m_collisionDebug = !m_collisionDebug; return m_collisionDebug; @@ -1154,6 +1188,10 @@ void WorldClient::collectLiquid(List<Vec2I> const& tilePositions, LiquidId liqui m_outgoingPackets.append(make_shared<CollectLiquidPacket>(tilePositions, liquidId)); } +void WorldClient::waitForLighting() { + MutexLocker lock(m_lightingMutex); +} + bool WorldClient::isTileProtected(Vec2I const& pos) const { if (!inWorld()) return true; @@ -1401,6 +1439,26 @@ RpcPromise<InteractAction> WorldClient::interact(InteractRequest const& request) return pair.first; } +void WorldClient::lightingMain() { + while (true) { + if (m_stopLightingThread) + return; + + MutexLocker locker(m_lightingMutex); + + if (m_renderData) { + m_lightingCalculator.calculate(m_renderData->lightMap); + m_renderData = nullptr; + } + + m_lightingCond.wait(m_lightingMutex); + continue; + + locker.unlock(); + Thread::yield(); + } +} + void WorldClient::initWorld(WorldStartPacket const& startPacket) { clearWorld(); m_outgoingPackets.append(make_shared<WorldStartAcknowledgePacket>()); diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 1a86f80..c5fbfd0 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -120,6 +120,8 @@ public: // Disable normal client-side lighting algorithm, everything full brightness. bool toggleFullbright(); + // Disable asynchronous client-side lighting algorithm, run on main thread. + bool toggleAsyncLighting(); // Spatial log generated collision geometry. bool toggleCollisionDebug(); @@ -153,6 +155,8 @@ public: void collectLiquid(List<Vec2I> const& tilePositions, LiquidId liquidId); + void waitForLighting(); + private: static const float DropDist; @@ -186,6 +190,8 @@ private: bool operator<(DamageNumberKey const& other) const; }; + void lightingMain(); + void initWorld(WorldStartPacket const& packet); void clearWorld(); void tryGiveMainPlayerItem(ItemPtr item); @@ -237,8 +243,16 @@ private: uint64_t m_currentStep; double m_currentServerStep; bool m_fullBright; + bool m_asyncLighting; CellularLightingCalculator m_lightingCalculator; mutable CellularLightIntensityCalculator m_lightIntensityCalculator; + ThreadFunction<void> m_lightingThread; + + mutable Mutex m_lightingMutex; + mutable ConditionVariable m_lightingCond; + mutable WorldRenderData* m_renderData; + bool m_stopLightingThread; + SkyPtr m_sky; CollisionGenerator m_collisionGenerator; diff --git a/source/game/StarWorldRenderData.hpp b/source/game/StarWorldRenderData.hpp index 1d1bc09..b13fef5 100644 --- a/source/game/StarWorldRenderData.hpp +++ b/source/game/StarWorldRenderData.hpp @@ -9,6 +9,7 @@ #include "StarParticle.hpp" #include "StarWeatherTypes.hpp" #include "StarEntity.hpp" +#include "StarThread.hpp" namespace Star { @@ -26,6 +27,7 @@ struct WorldRenderData { RenderTileArray tiles; Vec2I lightMinPosition; Image lightMap; + Image tileLightMap; List<EntityDrawables> entityDrawables; List<Particle> particles; diff --git a/source/game/interfaces/StarChattyEntity.hpp b/source/game/interfaces/StarChattyEntity.hpp index 03c1ac9..cd6ac8a 100644 --- a/source/game/interfaces/StarChattyEntity.hpp +++ b/source/game/interfaces/StarChattyEntity.hpp @@ -10,7 +10,7 @@ STAR_CLASS(ChattyEntity); class ChattyEntity : public virtual Entity { public: - virtual Vec2F mouthPosition() const { return mouthPosition(true); }; + virtual Vec2F mouthPosition() const = 0; virtual Vec2F mouthPosition(bool) const = 0; virtual List<ChatAction> pullPendingChatActions() = 0; }; diff --git a/source/game/interfaces/StarEntity.cpp b/source/game/interfaces/StarEntity.cpp index 405fdde..67887c9 100644 --- a/source/game/interfaces/StarEntity.cpp +++ b/source/game/interfaces/StarEntity.cpp @@ -115,6 +115,8 @@ void Entity::update(uint64_t) {} void Entity::render(RenderCallback*) {} +void Entity::renderLightSources(RenderCallback*) {} + EntityId Entity::entityId() const { return m_entityId; } diff --git a/source/game/interfaces/StarEntity.hpp b/source/game/interfaces/StarEntity.hpp index 3452654..ad297a9 100644 --- a/source/game/interfaces/StarEntity.hpp +++ b/source/game/interfaces/StarEntity.hpp @@ -153,6 +153,8 @@ public: virtual void render(RenderCallback* renderer); + virtual void renderLightSources(RenderCallback* renderer); + EntityId entityId() const; EntityDamageTeam getTeam() const; diff --git a/source/rendering/StarTilePainter.cpp b/source/rendering/StarTilePainter.cpp index 68e1110..fc115e5 100644 --- a/source/rendering/StarTilePainter.cpp +++ b/source/rendering/StarTilePainter.cpp @@ -38,7 +38,8 @@ void TilePainter::adjustLighting(WorldRenderData& renderData) const { RectI lightRange = RectI::withSize(renderData.lightMinPosition, Vec2I(renderData.lightMap.size())); forEachRenderTile(renderData, lightRange, [&](Vec2I const& pos, RenderTile const& tile) { // Only adjust lighting for full tiles - if (liquidDrawLevel(byteToFloat(tile.liquidLevel)) < 1.0f) + float drawLevel = liquidDrawLevel(byteToFloat(tile.liquidLevel)); + if (drawLevel == 0.0f) return; auto lightIndex = Vec2U(pos - renderData.lightMinPosition); @@ -46,7 +47,7 @@ void TilePainter::adjustLighting(WorldRenderData& renderData) const { auto const& liquid = m_liquids[tile.liquidId]; Vec3F tileLight = Vec3F(lightValue); - float darknessLevel = (1 - tileLight.sum() / (3.0f * 255.0f)); + float darknessLevel = (1 - tileLight.sum() / (3.0f * 255.0f)) * drawLevel; lightValue = Vec3B(tileLight.piecewiseMultiply(Vec3F::filled(1 - darknessLevel) + liquid.bottomLightMix * darknessLevel)); renderData.lightMap.set(lightIndex, lightValue); diff --git a/source/rendering/StarWorldPainter.cpp b/source/rendering/StarWorldPainter.cpp index 7cb7c72..a93a592 100644 --- a/source/rendering/StarWorldPainter.cpp +++ b/source/rendering/StarWorldPainter.cpp @@ -45,7 +45,7 @@ WorldCamera& WorldPainter::camera() { return m_camera; } -void WorldPainter::render(WorldRenderData& renderData) { +void WorldPainter::render(WorldRenderData& renderData, function<void()> lightWaiter) { m_camera.setScreenSize(m_renderer->screenSize()); m_camera.setTargetPixelRatio(Root::singleton().configuration()->get("zoomLevel").toFloat()); @@ -55,18 +55,6 @@ void WorldPainter::render(WorldRenderData& renderData) { m_tilePainter->setup(m_camera, renderData); - if (renderData.isFullbright) { - m_renderer->setEffectTexture("lightMap", Image::filled(Vec2U(1, 1), {255, 255, 255, 255}, PixelFormat::RGB24)); - m_renderer->setEffectParameter("lightMapMultiplier", 1.0f); - } else { - m_tilePainter->adjustLighting(renderData); - - m_renderer->setEffectParameter("lightMapMultiplier", m_assets->json("/rendering.config:lightMapMultiplier").toFloat()); - m_renderer->setEffectParameter("lightMapScale", Vec2F::filled(TilePixels * m_camera.pixelRatio())); - m_renderer->setEffectParameter("lightMapOffset", m_camera.worldToScreen(Vec2F(renderData.lightMinPosition))); - m_renderer->setEffectTexture("lightMap", renderData.lightMap); - } - // Stars, Debris Fields, Sky, and Orbiters m_environmentPainter->renderStars(m_camera.pixelRatio(), Vec2F(m_camera.screenSize()), renderData.skyRenderData); @@ -146,6 +134,10 @@ void WorldPainter::render(WorldRenderData& renderData) { m_tilePainter->cleanup(); } +void WorldPainter::adjustLighting(WorldRenderData& renderData) { + m_tilePainter->adjustLighting(renderData); +} + void WorldPainter::renderParticles(WorldRenderData& renderData, Particle::Layer layer) { const int textParticleFontSize = m_assets->json("/rendering.config:textParticleFontSize").toInt(); const RectF particleRenderWindow = RectF::withSize(Vec2F(), Vec2F(m_camera.screenSize())).padded(m_assets->json("/rendering.config:particleRenderWindowPadding").toInt()); diff --git a/source/rendering/StarWorldPainter.hpp b/source/rendering/StarWorldPainter.hpp index dac94fd..0ae6a7d 100644 --- a/source/rendering/StarWorldPainter.hpp +++ b/source/rendering/StarWorldPainter.hpp @@ -23,7 +23,8 @@ public: WorldCamera& camera(); - void render(WorldRenderData& renderData); + void render(WorldRenderData& renderData, function<void()> lightWaiter); + void adjustLighting(WorldRenderData& renderData); private: void renderParticles(WorldRenderData& renderData, Particle::Layer layer); |