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 CellularLightingCalculator::calculateAsync() { + return ThreadFunction([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 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 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 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 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()) + renderCallback->addLightSource(renderable.get()); + } + renderCallback->addLightSource({ position(), m_config->lightColor, m_config->pointLight, 0.0f, 0.0f, 0.0f }); } Maybe Projectile::receiveMessage(ConnectionId sendingConnection, String const& message, JsonArray const& args) { @@ -981,8 +987,6 @@ void Projectile::renderPendingRenderables(RenderCallback* renderCallback) { renderCallback->addAudio(renderable.get()); else if (renderable.is()) renderCallback->addParticle(renderable.get()); - else if (renderable.is()) - renderCallback->addLightSource(renderable.get()); } 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 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 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 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,75 +355,15 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { renderData.geometry = m_geometry; - 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); - - bool inspecting = m_mainPlayer->inspecting(); - float inspectionFlickerMultiplier = Random::randf(1 - Root::singleton().assets()->json("/highlights.config:inspectionFlickerAmount").toFloat(), 1); - - EntityId playerAimInteractive = NullEntityId; - if (Root::singleton().configuration()->get("interactiveHighlight").toBool()) { - if (auto entity = m_mainPlayer->bestInteractionEntity(false)) - playerAimInteractive = entity->entityId(); - } - - const List* directives = nullptr; - if (auto& worldTemplate = m_worldTemplate) { - if (const auto& parameters = worldTemplate->worldParameters()) - if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) - directives = &globalDirectives.get(); - } + ClientRenderCallback lightingRenderCallback; m_entityMap->forAllEntities([&](EntityPtr const& entity) { - if (m_startupHiddenEntities.contains(entity->entityId())) - return; + if (m_startupHiddenEntities.contains(entity->entityId())) + return; - ClientRenderCallback renderCallback; - entity->render(&renderCallback); + entity->renderLightSources(&lightingRenderCallback); + }); - EntityDrawables ed; - for (auto& p : renderCallback.drawables) { - if (directives) { - int directiveIndex = unsigned(entity->entityId()) % directives->size(); - for (auto& d : p.second) { - if (d.isImage()) - d.imagePart().addDirectives(directives->at(directiveIndex), true); - } - } - ed.layers[p.first] = move(p.second); - } - - if (m_interactiveHighlightMode || (!inspecting && entity->entityId() == playerAimInteractive)) { - if (auto interactive = as(entity)) { - if (interactive->isInteractive()) { - ed.highlightEffect.type = EntityHighlightEffectType::Interactive; - ed.highlightEffect.level = pulseLevel; - } - } - } else if (inspecting) { - if (auto inspectable = as(entity)) { - ed.highlightEffect = m_mainPlayer->inspectionHighlight(inspectable); - ed.highlightEffect.level *= inspectionFlickerMultiplier; - } - } - renderData.entityDrawables.append(move(ed)); - - renderLightSources.appendAll(move(renderCallback.lightSources)); - - if (directives) { - int directiveIndex = unsigned(entity->entityId()) % directives->size(); - for (auto& p : renderCallback.particles) - p.directives.append(directives->get(directiveIndex)); - } - - m_particles->addParticles(move(renderCallback.particles)); - m_samples.appendAll(move(renderCallback.audios)); - previewTiles.appendAll(move(renderCallback.previewTiles)); - renderData.overheadBars.appendAll(move(renderCallback.overheadBars)); - - }, [](EntityPtr const& a, EntityPtr const& b) { - return a->entityId() < b->entityId(); - }); + renderLightSources = move(lightingRenderCallback.lightSources); RectI window = m_clientState.window(); RectI tileRange = window.padded(bufferTiles); @@ -422,31 +373,11 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { 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; - - renderTile.background = clientTile.background; - renderTile.backgroundMod = clientTile.backgroundMod; - - renderTile.foregroundHueShift = clientTile.foregroundHueShift; - renderTile.foregroundModHueShift = clientTile.foregroundModHueShift; - renderTile.foregroundColorVariant = clientTile.foregroundColorVariant; - renderTile.foregroundDamageType = clientTile.foregroundDamage.damageType(); - renderTile.foregroundDamageLevel = floatToByte(clientTile.foregroundDamage.damageEffectPercentage()); - - renderTile.backgroundHueShift = clientTile.backgroundHueShift; - renderTile.backgroundModHueShift = clientTile.backgroundModHueShift; - renderTile.backgroundColorVariant = clientTile.backgroundColorVariant; - renderTile.backgroundDamageType = clientTile.backgroundDamage.damageType(); - renderTile.backgroundDamageLevel = floatToByte(clientTile.backgroundDamage.damageEffectPercentage()); - - renderTile.liquidId = clientTile.liquid.liquid; - renderTile.liquidLevel = floatToByte(clientTile.liquid.level); - }); - 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)); @@ -493,9 +424,107 @@ void WorldClient::render(WorldRenderData& renderData, unsigned bufferTiles) { m_lightingCalculator.addSpreadLight(position, Color::v3bToFloat(lightPair.second)); } - m_lightingCalculator.calculate(renderData.lightMap); + 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); + + bool inspecting = m_mainPlayer->inspecting(); + float inspectionFlickerMultiplier = Random::randf(1 - Root::singleton().assets()->json("/highlights.config:inspectionFlickerAmount").toFloat(), 1); + + EntityId playerAimInteractive = NullEntityId; + if (Root::singleton().configuration()->get("interactiveHighlight").toBool()) { + if (auto entity = m_mainPlayer->bestInteractionEntity(false)) + playerAimInteractive = entity->entityId(); + } + + const List* directives = nullptr; + if (auto& worldTemplate = m_worldTemplate) { + if (const auto& parameters = worldTemplate->worldParameters()) + if (auto& globalDirectives = m_worldTemplate->worldParameters()->globalDirectives) + directives = &globalDirectives.get(); + } + m_entityMap->forAllEntities([&](EntityPtr const& entity) { + if (m_startupHiddenEntities.contains(entity->entityId())) + return; + + ClientRenderCallback renderCallback; + + entity->render(&renderCallback); + + EntityDrawables ed; + for (auto& p : renderCallback.drawables) { + if (directives) { + int directiveIndex = unsigned(entity->entityId()) % directives->size(); + for (auto& d : p.second) { + if (d.isImage()) + d.imagePart().addDirectives(directives->at(directiveIndex), true); + } + } + ed.layers[p.first] = move(p.second); + } + + if (m_interactiveHighlightMode || (!inspecting && entity->entityId() == playerAimInteractive)) { + if (auto interactive = as(entity)) { + if (interactive->isInteractive()) { + ed.highlightEffect.type = EntityHighlightEffectType::Interactive; + ed.highlightEffect.level = pulseLevel; + } + } + } else if (inspecting) { + if (auto inspectable = as(entity)) { + ed.highlightEffect = m_mainPlayer->inspectionHighlight(inspectable); + ed.highlightEffect.level *= inspectionFlickerMultiplier; + } + } + renderData.entityDrawables.append(move(ed)); + + if (directives) { + int directiveIndex = unsigned(entity->entityId()) % directives->size(); + for (auto& p : renderCallback.particles) + p.directives.append(directives->get(directiveIndex)); + } + + m_particles->addParticles(move(renderCallback.particles)); + m_samples.appendAll(move(renderCallback.audios)); + previewTiles.appendAll(move(renderCallback.previewTiles)); + renderData.overheadBars.appendAll(move(renderCallback.overheadBars)); + + }, [](EntityPtr const& a, EntityPtr const& b) { + return a->entityId() < b->entityId(); + }); + + m_tileArray->tileEachTo(renderData.tiles, tileRange, [](RenderTile& renderTile, Vec2I const&, ClientTile const& clientTile) { + renderTile.foreground = clientTile.foreground; + renderTile.foregroundMod = clientTile.foregroundMod; + + renderTile.background = clientTile.background; + renderTile.backgroundMod = clientTile.backgroundMod; + + renderTile.foregroundHueShift = clientTile.foregroundHueShift; + renderTile.foregroundModHueShift = clientTile.foregroundModHueShift; + renderTile.foregroundColorVariant = clientTile.foregroundColorVariant; + renderTile.foregroundDamageType = clientTile.foregroundDamage.damageType(); + renderTile.foregroundDamageLevel = floatToByte(clientTile.foregroundDamage.damageEffectPercentage()); + + renderTile.backgroundHueShift = clientTile.backgroundHueShift; + renderTile.backgroundModHueShift = clientTile.backgroundModHueShift; + renderTile.backgroundColorVariant = clientTile.backgroundColorVariant; + renderTile.backgroundDamageType = clientTile.backgroundDamage.damageType(); + renderTile.backgroundDamageLevel = floatToByte(clientTile.backgroundDamage.damageEffectPercentage()); + + renderTile.liquidId = clientTile.liquid.liquid; + renderTile.liquidLevel = floatToByte(clientTile.liquid.level); + }); + 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 const& tilePositions, LiquidId liqui m_outgoingPackets.append(make_shared(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 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()); 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 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 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; List 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 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 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 lightWaiter); + void adjustLighting(WorldRenderData& renderData); private: void renderParticles(WorldRenderData& renderData, Particle::Layer layer);