#include "behavior.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../light/behavior.h" #include "../voxelizer/behavior.h" #include "../raytrace/behavior.h" #include "../../ext.h" #include "../../gui/gui.h" UF_BEHAVIOR_REGISTER_CPP(ext::ExtSceneBehavior) UF_BEHAVIOR_TRAITS_CPP(ext::ExtSceneBehavior, ticks = true, renders = false, multithread = false) // hangs on initialization #define this ((uf::Scene*) &self) void ext::ExtSceneBehavior::initialize( uf::Object& self ) { // auto& assetLoader = this->getComponent(); auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); this->addHook( "system:Quit.%UID%", [&](ext::json::Value& payload){ uf::renderer::settings::experimental::dedicatedThread = false; ext::ready = false; }); this->addHook( "asset:Load.%UID%", [&](pod::payloads::assetLoad& payload){ if ( !uf::asset::isExpected( payload, uf::asset::Type::AUDIO ) ) return; // if ( !uf::asset::has( payload ) ) uf::asset::load( payload ); auto& audio = uf::asset::get( payload ); // if ( !uf::asset::has(payload.filename) ) uf::asset::load( payload ); // auto& audio = this->getComponent(); #if UF_AUDIO_MAPPED_VOLUMES audio.setVolume(uf::audio::volumes.count("bgm") > 0 ? uf::audio::volumes.at("bgm") : 1.0); #else audio.setVolume(uf::audio::volumes::bgm); #endif audio.loop( true ); audio.play(); if ( !payload.asComponent ) { this->moveComponent( uf::asset::get( payload.filename ) ); } }); this->addHook( "menu:Open", [&](pod::payloads::menuOpen& payload){ TIMER(1) { uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); if ( !manager ) return; uf::stl::string key = payload.name; uf::Object& gui = manager->loadChild(metadataJson["menus"][key].as("/entites/gui/"+key+"/menu.json"), false); uf::Serializer& metadataJson = gui.getComponent(); gui.initialize(); }; }); this->addHook( "world:Entity.LoadAsset", [&](pod::payloads::assetLoad& payload){ if ( !payload.object ) return; uf::asset::load("asset:Load." + std::to_string(payload.object.uid), payload); }); this->addHook( "shader:Update.%UID%", [&](ext::json::Value& json){ metadata.shader.mode = json["mode"].as(); metadata.shader.scalar = json["scalar"].as(); metadata.shader.parameters = uf::vector::decode( json["parameters"], metadata.shader.parameters ); ext::json::forEach( metadataJson["system"]["renderer"]["shader"]["parameters"], [&]( uint32_t i, const ext::json::Value& value ){ if ( value.as() == "time" ) metadata.shader.time = i; }); if ( 0 <= metadata.shader.time && metadata.shader.time < 4 ) { metadata.shader.parameters[metadata.shader.time] = uf::physics::time::current; } }); /* store viewport size */ #if 0 this->addHook( "window:Resized", [&](pod::payloads::windowResized& payload){ ext::gui::size.current = payload.window.size; }); #endif /* Spawn an entity */ this->addHook( "entity:Spawn", [&](pod::payloads::entitySpawn& payload){ if ( payload.parent.uid ) { payload.parent.pointer = (uf::Object*) this->globalFindByUid(payload.parent.uid); } if ( !payload.parent.pointer ) payload.parent.pointer = this; uf::Object* child = NULL; if ( payload.filename != "" ) child = payload.parent.pointer->loadChildPointer( payload.filename ); else child = payload.parent.pointer->loadChildPointer( payload.metadata ); if ( !child ) return; auto& transform = child->getComponent>(); if ( payload.transform.position != pod::Vector3f{0,0,0} ) transform.position = payload.transform.position; if ( payload.transform.orientation != uf::quaternion::identity() ) transform.orientation = payload.transform.orientation; if ( !payload.transform.reference ) transform.reference = payload.transform.reference; }); UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); // lock control { pod::payloads::windowMouseCursorVisibility payload; payload.mouse.visible = false; uf::hooks.call("window:Mouse.CursorVisibility", payload); uf::hooks.call("window:Mouse.Lock"); } auto& sceneTextures = this->getComponent(); // initialize perlin noise #if UF_USE_VULKAN { auto& texture = sceneTextures.noise; //this->getComponent(); texture.sampler.descriptor.addressMode = { uf::renderer::enums::AddressMode::MIRRORED_REPEAT, uf::renderer::enums::AddressMode::MIRRORED_REPEAT, uf::renderer::enums::AddressMode::MIRRORED_REPEAT }; auto& noiseGenerator = this->getComponent(); noiseGenerator.seed(rand()); float high = std::numeric_limits::min(); float low = std::numeric_limits::max(); float amplitude = metadataJson["noise"]["amplitude"].is() ? metadataJson["noise"]["amplitude"].as() : 1.5; pod::Vector3ui size = uf::vector::decode(metadataJson["noise"]["size"], pod::Vector3ui{64, 64, 64}); pod::Vector3d coefficients = uf::vector::decode(metadataJson["noise"]["coefficients"], pod::Vector3d{3.0, 3.0, 3.0}); uf::stl::vector pixels(size.x * size.y * size.z); uf::stl::vector perlins(size.x * size.y * size.z); #pragma omp parallel for for ( uint32_t z = 0; z < size.z; ++z ) { for ( uint32_t y = 0; y < size.y; ++y ) { for ( uint32_t x = 0; x < size.x; ++x ) { float nx = (float) x / (float) size.x; float ny = (float) y / (float) size.y; float nz = (float) z / (float) size.z; float n = amplitude * noiseGenerator.noise(coefficients.x * nx, coefficients.y * ny, coefficients.z * nz); high = std::max( high, n ); low = std::min( low, n ); perlins[x + y * size.x + z * size.x * size.y] = n; } } } for ( uint32_t i = 0; i < perlins.size(); ++i ) { float n = perlins[i]; n = n - floor(n); float normalized = (n - low) / (high - low); if ( normalized >= 1.0f ) normalized = 1.0f; pixels[i] = static_cast(floor(normalized * 255)); } texture.fromBuffers( (void*) pixels.data(), pixels.size(), uf::renderer::enums::Format::R8_UNORM, size.x, size.y, size.z, 1 ); } // initialize cubemap { const uf::stl::vector filenames = { "front", "back", "up", "down", "right", "left", }; uf::Image::container_t pixels; uf::stl::vector images(filenames.size()); pod::Vector2ui size = {0,0}; auto& texture = sceneTextures.skybox; for ( uint32_t i = 0; i < filenames.size(); ++i ) { auto filename = uf::string::replace( this->resolveURI(metadata.sky.box.filename), "%d", filenames[i] ); auto& image = images[i]; image.open(filename); image.flip(); if ( size.x == 0 && size.y == 0 ) size = image.getDimensions(); else if ( size != image.getDimensions() ) UF_EXCEPTION("ERROR: MISMATCH CUBEMAP FACE SIZE"); auto& p = image.getPixels(); pixels.reserve( pixels.size() + p.size() ); pixels.insert( pixels.end(), p.begin(), p.end() ); } // texture.mips = 0; if ( size.x > 0 && size.y > 0 ) { texture.fromBuffers( (void*) pixels.data(), pixels.size(), uf::renderer::enums::Format::R8G8B8A8_UNORM, size.x, size.y, 1, filenames.size() ); } } #endif } void ext::ExtSceneBehavior::tick( uf::Object& self ) { // auto& assetLoader = this->getComponent(); uf::asset::processQueue(); auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); uf::hooks.call("game:Frame.Start"); ++metadata.shader.frameAccumulate; if ( !metadata.shader.frameAccumulateReset && metadata.shader.frameAccumulateLimit && metadata.shader.frameAccumulate > metadata.shader.frameAccumulateLimit ) { metadata.shader.frameAccumulateReset = true; } if ( metadata.shader.frameAccumulateReset ) { metadata.shader.frameAccumulate = 0; metadata.shader.frameAccumulateReset = false; } static bool lagged = false; /* funi sound */ if ( lagged || uf::time::current - uf::time::previous > 5 ) { lagged = true; auto& controller = this->getController().as(); if ( controller.getUid() != this->getUid() ) { lagged = false; ext::json::Value payload; payload["filename"] = "/ui/pl_drown1.ogg"; payload["volume"] = "sfx"; payload["spatial"] = true; controller.queueHook("sound:Emit.%UID%", payload); } } // uf::renderer::states::frameAccumulate = metadata.shader.frameAccumulate; // uf::renderer::states::frameAccumulateReset = metadata.shader.frameAccumulateReset; /* Print World Tree */ { TIMER(1, uf::inputs::kbm::states::U || uf::scene::printTaskCalls ) { std::function filter = []( uf::Entity* entity, int indent ) { for ( int i = 0; i < indent; ++i ) uf::iostream << "\t"; uf::iostream << uf::string::toString(entity->as()) << " "; if ( entity->hasComponent>() ) { pod::Transform<> t = uf::transform::flatten(entity->getComponent>()); uf::iostream << uf::string::toString(t.position) << " " << uf::string::toString(t.orientation); } uf::iostream << "\n"; }; for ( uf::Scene* scene : uf::scene::scenes ) { if ( !scene ) continue; uf::iostream << "Scene: " << scene->getName() << ": " << scene << "\n"; scene->process(filter, 1); } } } #if UF_USE_VULKAN /* Screenshot */ { TIMER(1, uf::inputs::kbm::states::F11 ) { auto& renderMode = uf::renderer::getRenderMode("Swapchain", true); auto image = renderMode.screenshot(uf::renderer::states::currentBuffer ? 0 : 1); std::time_t t = std::time(nullptr); const uf::stl::string filename = ::fmt::format("{}/screenshots/{:%Y-%m-%d_%H-%M-%S}.png", uf::io::root, ::fmt::localtime(t)); image.save(filename); UF_MSG_DEBUG("Screenshot saved to {}", filename); } } #if 0 /* Force Rebuild */ { TIMER(1, uf::inputs::kbm::states::R ) { uf::renderer::states::rebuild = true; UF_MSG_DEBUG("Rebuild requested"); } } #endif #endif #if 0 /* Mark as ready for multithreading */ { TIMER(1, uf::inputs::kbm::states::M ) { uf::renderer::settings::experimental::dedicatedThread = !uf::renderer::settings::experimental::dedicatedThread; UF_MSG_DEBUG("Toggling multithreaded rendering..."); } } /* Print World Tree */ { TIMER(1, uf::inputs::kbm::states::U ) { std::function filter = []( uf::Entity* entity, int indent ) { for ( int i = 0; i < indent; ++i ) uf::iostream << "\t"; uf::iostream << uf::string::toString(entity->as()) << " ["; for ( auto& behavior : entity->getBehaviors() ) { uf::iostream << uf::instantiator::behaviors->names[behavior.type] << ", "; } uf::iostream << "]\n"; }; for ( uf::Scene* scene : uf::scene::scenes ) { if ( !scene ) continue; uf::iostream << "Scene: " << scene->getName() << ": " << scene << "\n"; scene->process(filter, 1); } uf::Serializer instantiator; { int i = 0; for ( auto& pair : uf::instantiator::objects->names ) { instantiator["objects"][i++] = pair.second; } } { int i = 0; for ( auto& pair : uf::instantiator::behaviors->names ) { instantiator["behaviors"][i++] = pair.second; } } uf::iostream << instantiator << "\n"; } } #endif #if UF_USE_OPENAL /* check if audio needs to loop */ { auto& audio = this->getComponent(); if ( !audio.playing() ) audio.play(); } /* Updates Sound Listener */ { auto& controller = this->getController(); // copy pod::Transform<> transform = controller.getComponent>(); if ( controller.hasComponent() ) { auto& camera = controller.getComponent(); transform.position += camera.getTransform().position; transform = uf::transform::reorient( transform ); } transform.forward *= -1; ext::al::listener( transform ); } #if 0 /* check if audio needs to loop */ { auto& bgm = this->getComponent(); float current = bgm.getTime(); float end = bgm.getDuration(); float epsilon = 0.005f; if ( (current + epsilon >= end || !bgm.playing()) && !bgm.loops() ) { // intro to main transition uf::stl::string filename = bgm.getFilename(); filename = uf::asset::getOriginal(filename); if ( filename.find("_intro") != uf::stl::string::npos ) { uf::asset::load(uf::string::replace( filename, "_intro", "" ), this->formatHookName("asset:Load.%UID%")); // loop } else { bgm.setTime(0); if ( !bgm.playing() ) bgm.play(); } } } #endif #endif #if !UF_ENV_DREAMCAST /* Regain control if nothing requests it */ { pod::payloads::windowMouseCursorVisibility payload; payload.mouse.visible = this->globalFindByName("Gui: Menu"); if ( !payload.mouse.visible ) uf::hooks.call("window:Mouse.Lock"); uf::hooks.call("window:Mouse.CursorVisibility", payload); } #endif #if UF_ENTITY_METADATA_USE_JSON metadata.deserialize(self, metadataJson); #else if ( 0 <= metadata.shader.time && metadata.shader.time < 4 ) { metadata.shader.parameters[metadata.shader.time] = uf::physics::time::current; } #endif #if UF_USE_OPENGL if ( metadata.light.enabled ) { auto/*&*/ graph = this->getGraph(); auto& controller = this->getController(); // auto& camera = controller.getComponent(); auto& camera = this->getCamera( controller ); auto& controllerMetadata = controller.getComponent(); auto& controllerTransform = controller.getComponent>(); auto& metadata = this->getComponent(); auto& metadataVxgi = this->getComponent(); auto& metadataJson = this->getComponent(); struct LightInfo { uf::Entity* entity = NULL; pod::Vector4f position = {0,0,0,1}; // OpenGL requires a W pod::Vector4f color = {0,0,0,1}; // OpenGL requires an alpha float distance = 0; float power = 0; bool global = 0; }; uf::stl::vector entities; for ( auto entity : graph ) { if ( entity == this || entity == &controller || !entity->hasComponent() ) continue; auto& metadata = entity->getComponent(); if ( metadata.power <= 0 ) continue; auto flatten = uf::transform::flatten( entity->getComponent>() ); LightInfo& info = entities.emplace_back(LightInfo{ .entity = entity, .position = flatten.position, .color = metadata.color, .distance = uf::vector::magnitude( uf::vector::subtract( flatten.position, controllerTransform.position ) ), .power = metadata.power, .global = metadata.global, }); info.position.w = 1; info.color.w = 1; } std::sort( entities.begin(), entities.end(), [&]( LightInfo& l, LightInfo& r ){ if ( l.global && !r.global ) return true; if ( !l.global && r.global ) return false; return l.distance < r.distance; }); static GLint glMaxLights = 0; if ( !glMaxLights ) glGetIntegerv(GL_MAX_LIGHTS, &glMaxLights); metadata.light.max = std::min( (uint32_t) glMaxLights, metadata.light.max ); // add lighting { uint32_t i = 0; for ( ; i < entities.size() && i < metadata.light.max; ++i ) { auto& info = entities[i]; // uf::Entity* entity = info.entity; GLenum target = GL_LIGHT0+i; GL_ERROR_CHECK(glEnable(target)); GL_ERROR_CHECK(glLightfv(target, GL_AMBIENT, &metadata.light.ambient[0])); GL_ERROR_CHECK(glLightfv(target, GL_SPECULAR, &metadata.light.specular[0])); GL_ERROR_CHECK(glLightfv(target, GL_DIFFUSE, &info.color[0])); GL_ERROR_CHECK(glLightfv(target, GL_POSITION, &info.position[0])); GL_ERROR_CHECK(glLightf(target, GL_CONSTANT_ATTENUATION, 0.0f)); GL_ERROR_CHECK(glLightf(target, GL_LINEAR_ATTENUATION, 0)); GL_ERROR_CHECK(glLightf(target, GL_QUADRATIC_ATTENUATION, 1.0f / info.power)); } for ( ; i < metadata.light.max; ++i ) GL_ERROR_CHECK(glDisable(GL_LIGHT0+i)); } } #elif UF_USE_VULKAN { auto/*&*/ graph = this->getGraph(); auto& controller = this->getController(); // auto& camera = controller.getComponent(); auto& camera = this->getCamera( controller ); auto& controllerMetadata = controller.getComponent(); auto& controllerTransform = controller.getComponent>(); auto& metadata = this->getComponent(); auto& metadataVxgi = this->getComponent(); auto& metadataJson = this->getComponent(); struct LightInfo { uf::Entity* entity = NULL; pod::Vector3f position = {0,0,0}; float range = 0.0f; pod::Vector3f color = {0,0,0}; float intensity = 0.0f; float distance = 0; float bias = 0; int32_t type = 0; bool shadows = false; bool global = false; }; uf::stl::vector entities; entities.reserve(graph.size() / 2); uf::graph::storage.lights.clear(); uf::graph::storage.lights.reserve(metadata.light.max); uf::graph::storage.shadow2Ds.clear(); uf::graph::storage.shadow2Ds.reserve(metadata.light.max); uf::graph::storage.shadowCubes.clear(); uf::graph::storage.shadowCubes.reserve(metadata.light.max); // traverse scene graph for ( auto entity : graph ) { // ignore this scene, our controller, and anything that isn't actually a light if ( entity == this || entity == &controller || !entity->hasComponent() ) continue; auto& metadata = entity->getComponent(); // disables shadow mappers that activate when in range bool hasRT = entity->hasComponent(); if ( hasRT ) { auto& renderMode = entity->getComponent(); if ( metadata.renderer.mode == "in-range" ) { renderMode.execute = false; renderMode.metadata.limiter.execute = false; } } if ( metadata.power <= 0 ) continue; auto flatten = uf::transform::flatten( entity->getComponent>() ); LightInfo& info = entities.emplace_back(LightInfo{ .entity = entity, .position = flatten.position, .range = 0, .color = pod::Vector4f{ metadata.color.x, metadata.color.y, metadata.color.z }, .intensity = metadata.power, .distance = uf::vector::magnitude( uf::vector::subtract( flatten.position, controllerTransform.position ) ), .bias = metadata.bias, .type = metadata.type, .shadows = metadata.shadows && hasRT, .global = metadata.global, }); } // prioritize closer lights; it would be nice to also prioritize lights in view, but because of VXGI it's not really something to do std::sort( entities.begin(), entities.end(), [&]( LightInfo& l, LightInfo& r ){ if ( l.global && !r.global ) return true; if ( !l.global && r.global ) return false; return l.distance < r.distance; }); int32_t shadowUpdateThreshold = metadata.shadow.update; // how many shadow maps we should update, based on range int32_t shadowCount = metadata.shadow.max; // how many shadow maps we should pass, based on range if ( shadowCount <= 0 ) shadowCount = std::numeric_limits::max(); // disable shadows if that light is outside our threshold for ( auto& info : entities ) if ( info.shadows && shadowCount-- <= 0 ) info.shadows = false; // bind lighting and requested shadow maps for ( uint32_t i = 0; i < entities.size() && uf::graph::storage.lights.size() < metadata.light.max; ++i ) { auto& info = entities[i]; uf::Entity* entity = info.entity; if ( !info.shadows ) { uf::graph::storage.lights.emplace_back(pod::Light{ .view = uf::matrix::identity(), .projection = uf::matrix::identity(), .position = info.position, .range = info.range, .color = info.color, .intensity = info.intensity, .type = info.type, .typeMap = 0, .indexMap = -1, .depthBias = info.bias, }); } else { auto& renderMode = entity->getComponent(); auto& lightCamera = entity->getComponent(); auto& lightMetadata = entity->getComponent(); lightMetadata.renderer.rendered = true; // activate our shadow mapper if it's range-basedd if ( lightMetadata.renderer.mode == "in-range" && shadowUpdateThreshold-- > 0 ) { renderMode.execute = true; renderMode.metadata.limiter.execute = true; } constexpr uint32_t MODE_SPLIT = 0; constexpr uint32_t MODE_CUBEMAP = 1; constexpr uint32_t MODE_SEPARATE_2DS = 2; // if point light, and combining is requested if ( metadata.shadow.typeMap > MODE_SPLIT && renderMode.renderTarget.views == 6 ) { int32_t index = -1; // separated texture2Ds if ( metadata.shadow.typeMap == MODE_SEPARATE_2DS ) { UF_MSG_WARNING("deprecated feature used: separate Texture2Ds for shadow maps"); index = uf::graph::storage.shadow2Ds.size(); for ( auto& attachment : renderMode.renderTarget.attachments ) { if ( !(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ) continue; for ( size_t view = 0; view < renderMode.renderTarget.views; ++view ) { uf::graph::storage.shadow2Ds.emplace_back().aliasAttachment(attachment, view); } break; } // cubemapped } else if ( metadata.shadow.typeMap == MODE_CUBEMAP ) { index = uf::graph::storage.shadowCubes.size(); for ( auto& attachment : renderMode.renderTarget.attachments ) { if ( !(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ) continue; uf::graph::storage.shadowCubes.emplace_back().aliasAttachment(attachment); break; } } uf::graph::storage.lights.emplace_back(pod::Light{ .view = lightCamera.getView(0), .projection = lightCamera.getProjection(0), .position = info.position, .range = info.range, .color = info.color, .intensity = info.intensity, .type = info.type, .typeMap = metadata.shadow.typeMap, .indexMap = index, .depthBias = info.bias, }); // any other shadowing light, even point lights, are split by shadow maps } else { for ( auto& attachment : renderMode.renderTarget.attachments ) { if ( !(attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ) continue; if ( attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) continue; for ( size_t view = 0; view < renderMode.renderTarget.views; ++view ) { uf::graph::storage.lights.emplace_back(pod::Light{ .view = lightCamera.getView(view), .projection = lightCamera.getProjection(view), .position = info.position, .range = info.range, .color = info.color, .intensity = info.intensity, .type = info.type, .typeMap = 0, .indexMap = uf::graph::storage.shadow2Ds.size(), .depthBias = info.bias, }); uf::graph::storage.shadow2Ds.emplace_back().aliasAttachment(attachment, view); } } } } } for ( auto& texture : uf::graph::storage.shadowCubes ) { texture.sampler.descriptor.filter.min = uf::renderer::enums::Filter::LINEAR; texture.sampler.descriptor.filter.mag = uf::renderer::enums::Filter::LINEAR; } for ( auto& texture : uf::graph::storage.shadow2Ds ) { texture.sampler.descriptor.filter.min = uf::renderer::enums::Filter::LINEAR; texture.sampler.descriptor.filter.mag = uf::renderer::enums::Filter::LINEAR; } uf::graph::storage.buffers.light.update( (const void*) uf::graph::storage.lights.data(), uf::graph::storage.lights.size() * sizeof(pod::Light) ); } #endif #if UF_USE_VULKAN /* Update lights */ if ( uf::renderer::settings::pipelines::deferred && !uf::renderer::settings::pipelines::vxgi ) { auto& deferredRenderMode = uf::renderer::getRenderMode("", true); auto& deferredBlitter = deferredRenderMode.getBlitter(); if ( deferredBlitter.material.hasShader("compute", "deferred") ) { ext::ExtSceneBehavior::bindBuffers( *this, "", "compute", "deferred" ); } else { ext::ExtSceneBehavior::bindBuffers( *this, "", "fragment", "deferred" ); } } #endif /* Update post processing */ { auto& renderMode = uf::renderer::getRenderMode("", true); auto& blitter = renderMode.getBlitter(); if ( blitter.material.hasShader("fragment") ) { auto& shader = blitter.material.getShader("fragment"); struct { float curTime = 0; float gamma = 1.0; float exposure = 1.0; uint32_t padding = 0; } uniforms = { .curTime = uf::time::current, .gamma = metadata.light.gamma, .exposure = metadata.light.exposure }; shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") ); } } } void ext::ExtSceneBehavior::render( uf::Object& self ) {} void ext::ExtSceneBehavior::destroy( uf::Object& self ) { if ( this->hasComponent() ) { auto& sceneTextures = this->getComponent(); for ( auto& t : sceneTextures.voxels.id ) t.destroy(); sceneTextures.voxels.id.clear(); for ( auto& t : sceneTextures.voxels.normal ) t.destroy(); sceneTextures.voxels.normal.clear(); for ( auto& t : sceneTextures.voxels.uv ) t.destroy(); sceneTextures.voxels.uv.clear(); for ( auto& t : sceneTextures.voxels.radiance ) t.destroy(); sceneTextures.voxels.radiance.clear(); for ( auto& t : sceneTextures.voxels.depth ) t.destroy(); sceneTextures.voxels.depth.clear(); sceneTextures.noise.destroy(); sceneTextures.skybox.destroy(); } } void ext::ExtSceneBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) { serializer["light"]["should"] = /*this->*/light.enabled; serializer["light"]["ambient"] = uf::vector::encode( /*this->*/light.ambient ); serializer["light"]["exposure"] = /*this->*/light.exposure; serializer["light"]["gamma"] = /*this->*/light.gamma; serializer["light"]["useLightmaps"] = /*this->*/light.useLightmaps; serializer["light"]["fog"]["color"] = uf::vector::encode( /*this->*/fog.color ); serializer["light"]["fog"]["step scale"] = /*this->*/fog.stepScale; serializer["light"]["fog"]["absorbtion"] = /*this->*/fog.absorbtion; serializer["light"]["fog"]["range"] = uf::vector::encode( /*this->*/fog.range ); serializer["light"]["fog"]["density"]["offset"] = uf::vector::encode( /*this->*/fog.density.offset ); serializer["light"]["fog"]["density"]["timescale"] = /*this->*/fog.density.timescale; serializer["light"]["fog"]["density"]["threshold"] = /*this->*/fog.density.threshold; serializer["light"]["fog"]["density"]["multiplier"] = /*this->*/fog.density.multiplier; serializer["light"]["fog"]["density"]["scale"] = /*this->*/fog.density.scale; serializer["sky"]["box"]["filename"] = /*this->*/sky.box.filename; serializer["system"]["renderer"]["shader"]["mode"] = /*this->*/shader.mode; serializer["system"]["renderer"]["shader"]["scalar"] = /*this->*/shader.scalar; serializer["system"]["renderer"]["shader"]["parameters"] = uf::vector::encode( /*this->*/shader.parameters ); } void ext::ExtSceneBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ) { // merge light settings with global settings { const auto& globalSettings = ext::config["engine"]["scenes"]["lights"]; ext::json::forEach( globalSettings, [&]( const uf::stl::string& key, const ext::json::Value& value ){ if ( !ext::json::isNull( serializer["light"][key] ) ) return; serializer["light"][key] = value; } ); } // merge bloom settings with global settings { const auto& globalSettings = ext::config["engine"]["scenes"]["lights"]["bloom"]; ext::json::forEach( globalSettings, [&]( const uf::stl::string& key, const ext::json::Value& value ){ if ( !ext::json::isNull( serializer["light"]["bloom"][key] ) ) return; serializer["light"]["bloom"][key] = value; } ); } // merge shadows settings with global settings { const auto& globalSettings = ext::config["engine"]["scenes"]["lights"]["shadows"]; ext::json::forEach( globalSettings, [&]( const uf::stl::string& key, const ext::json::Value& value ){ if ( !ext::json::isNull( serializer["light"]["shadows"][key] ) ) return; serializer["light"]["shadows"][key] = value; } ); } // merge fog settings with global settings { const auto& globalSettings = ext::config["engine"]["scenes"]["lights"]["fog"]; ext::json::forEach( globalSettings, [&]( const uf::stl::string& key, const ext::json::Value& value ){ if ( !ext::json::isNull( serializer["light"]["fog"][key] ) ) return; serializer["light"]["fog"][key] = value; } ); } /*this->*/max.textures2D = ext::config["engine"]["scenes"]["textures"]["max"]["2D"].as(/*this->*/max.textures2D); /*this->*/max.texturesCube = ext::config["engine"]["scenes"]["textures"]["max"]["cube"].as(/*this->*/max.texturesCube); /*this->*/max.textures3D = ext::config["engine"]["scenes"]["textures"]["max"]["3D"].as(/*this->*/max.textures3D); /*this->*/shadow.enabled = serializer["light"]["shadows"]["enabled"].as(/*this->*/shadow.enabled); /*this->*/shadow.samples = serializer["light"]["shadows"]["samples"].as(/*this->*/shadow.samples); /*this->*/shadow.max = serializer["light"]["shadows"]["max"].as(/*this->*/shadow.max); /*this->*/shadow.update = serializer["light"]["shadows"]["update"].as(/*this->*/shadow.update); /*this->*/shadow.typeMap = serializer["light"]["shadows"]["map type"].as(/*this->*/shadow.typeMap); /*this->*/light.enabled = serializer["light"]["enabled"].as(/*this->*/light.enabled) && serializer["light"]["should"].as(/*this->*/light.enabled); /*this->*/light.max = serializer["light"]["max"].as(/*this->*/light.max); /*this->*/light.ambient = uf::vector::decode( serializer["light"]["ambient"], /*this->*/light.ambient); // if ( uf::renderer::settings::pipelines::fsr ) serializer["light"]["exposure"] = 1; /*this->*/light.exposure = serializer["light"]["exposure"].as(/*this->*/light.exposure); /*this->*/light.gamma = serializer["light"]["gamma"].as(/*this->*/light.gamma); /*this->*/light.useLightmaps = serializer["light"]["useLightmaps"].as(/*this->*/light.useLightmaps); /*this->*/bloom.threshold = serializer["light"]["bloom"]["threshold"].as(/*this->*/bloom.threshold); /*this->*/bloom.scale = serializer["light"]["bloom"]["scale"].as(/*this->*/bloom.scale); /*this->*/bloom.strength = serializer["light"]["bloom"]["strength"].as(/*this->*/bloom.strength); /*this->*/bloom.sigma = serializer["light"]["bloom"]["sigma"].as(/*this->*/bloom.sigma); /*this->*/bloom.samples = serializer["light"]["bloom"]["samples"].as(/*this->*/bloom.samples); /*this->*/fog.color = uf::vector::decode( serializer["light"]["fog"]["color"], /*this->*/fog.color ); /*this->*/fog.stepScale = serializer["light"]["fog"]["step scale"].as( /*this->*/fog.stepScale ); /*this->*/fog.absorbtion = serializer["light"]["fog"]["absorbtion"].as( /*this->*/fog.absorbtion ); /*this->*/fog.range = uf::vector::decode( serializer["light"]["fog"]["range"], /*this->*/fog.range ); /*this->*/fog.density.offset = uf::vector::decode( serializer["light"]["fog"]["density"]["offset"], /*this->*/fog.density.offset ); /*this->*/fog.density.timescale = serializer["light"]["fog"]["density"]["timescale"].as(/*this->*/fog.density.timescale); /*this->*/fog.density.threshold = serializer["light"]["fog"]["density"]["threshold"].as(/*this->*/fog.density.threshold); /*this->*/fog.density.multiplier = serializer["light"]["fog"]["density"]["multiplier"].as(/*this->*/fog.density.multiplier); /*this->*/fog.density.scale = serializer["light"]["fog"]["density"]["scale"].as(/*this->*/fog.density.scale); /*this->*/sky.box.filename = serializer["sky"]["box"]["filename"].as(/*this->*/sky.box.filename); /*this->*/shader.mode = serializer["system"]["renderer"]["shader"]["mode"].as(/*this->*/shader.mode); /*this->*/shader.scalar = serializer["system"]["renderer"]["shader"]["scalar"].as(/*this->*/shader.scalar); /*this->*/shader.parameters = uf::vector::decode( serializer["system"]["renderer"]["shader"]["parameters"], /*this->*/shader.parameters ); /*this->*/shader.frameAccumulateLimit = serializer["system"]["renderer"]["shader"]["frame accumulate limit"].as(/*this->*/shader.frameAccumulateLimit); #if UF_AUDIO_MAPPED_VOLUMES ext::json::forEach( serializer["volumes"], []( const uf::stl::string& key, ext::json::Value& value ){ float volume; volume = value.as(volume); uf::audio::volumes[key] = volume; }); #else if ( ext::json::isObject( serializer["volumes"] ) ) { uf::audio::volumes::bgm = serializer["volumes"]["bgm"].as(uf::audio::volumes::bgm); uf::audio::volumes::sfx = serializer["volumes"]["sfx"].as(uf::audio::volumes::sfx); uf::audio::volumes::voice = serializer["volumes"]["voice"].as(uf::audio::volumes::voice); } #endif ext::json::forEach( serializer["system"]["renderer"]["shader"]["parameters"], [&]( uint32_t i, const ext::json::Value& value ){ if ( value.as() == "time" ) /*this->*/shader.time = i; }); if ( 0 <= /*this->*/shader.time && /*this->*/shader.time < 4 ) { /*this->*/shader.parameters[/*this->*/shader.time] = uf::physics::time::current; } #if UF_USE_OPENGL_FIXED_FUNCTION uf::renderer::states::rebuild = true; if ( light.enabled ) { GL_ERROR_CHECK(glEnable(GL_LIGHTING)); } else { GL_ERROR_CHECK(glDisable(GL_LIGHTING)); } #endif #if UF_USE_VULKAN if ( uf::renderer::settings::pipelines::bloom ) { auto& renderMode = uf::renderer::getRenderMode("", true); auto& blitter = renderMode.getBlitter(); auto& shader = blitter.material.getShader("compute", "bloom"); struct UniformDescriptor { float scale; float strength; float threshold; float sigma; float gamma; float exposure; uint32_t samples; uint32_t padding; }; UniformDescriptor uniforms = { .scale = bloom.scale, .strength = bloom.strength, .threshold = bloom.threshold, .sigma = bloom.sigma, .gamma = light.gamma, .exposure = light.exposure, .samples = bloom.samples, }; shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") ); } #endif } void ext::ExtSceneBehavior::bindBuffers( uf::Object& self, const uf::stl::string& renderModeName, const uf::stl::string& shaderType, const uf::stl::string& shaderPipeline ) { auto& renderMode = uf::renderer::getRenderMode(renderModeName, true); bindBuffers( self, renderMode.getBlitter(), shaderType, shaderPipeline ); } void ext::ExtSceneBehavior::bindBuffers( uf::Object& self, uf::renderer::Graphic& graphic, const uf::stl::string& shaderType, const uf::stl::string& shaderPipeline ) { auto/*&*/ graph = this->getGraph(); auto& controller = this->getController(); // auto& camera = controller.getComponent(); auto& camera = this->getCamera( controller ); auto& controllerMetadata = controller.getComponent(); auto& controllerTransform = controller.getComponent>(); auto& metadata = this->getComponent(); auto& metadataVxgi = this->getComponent(); auto& metadataRt = this->getComponent(); auto& metadataJson = this->getComponent(); if ( !graphic.initialized ) return; #if UF_USE_VULKAN struct UniformDescriptor { struct Matrices { alignas(16) pod::Matrix4f view; alignas(16) pod::Matrix4f projection; alignas(16) pod::Matrix4f model; alignas(16) pod::Matrix4f previous; alignas(16) pod::Matrix4f iView; alignas(16) pod::Matrix4f iProjection; // alignas(16) pod::Matrix4f iProjectionView; alignas(16) pod::Vector4f eyePos; } matrices[2]; struct Settings { struct Lengths { alignas(4) uint32_t lights = 0; alignas(4) uint32_t materials = 0; alignas(4) uint32_t textures = 0; alignas(4) uint32_t drawCommands = 0; } lengths; struct Mode { alignas(16) pod::Vector4f parameters; alignas(4) uint32_t mode; alignas(4) uint32_t scalar; alignas(4) uint32_t msaa; alignas(4) uint32_t frameAccumulate; } mode; struct Lighting { pod::Vector3f ambient; alignas(4) uint32_t indexSkybox; alignas(4) uint32_t maxShadows; alignas(4) uint32_t shadowSamples; alignas(4) uint32_t useLightmaps; } lighting; struct Fog { pod::Vector3f color; alignas(4) float stepScale; pod::Vector3f offset; alignas(4) float densityScale; alignas(8) pod::Vector2f range; alignas(4) float densityThreshold; alignas(4) float densityMultiplier; alignas(4) float absorbtion; alignas(4) float padding1; alignas(4) float padding2; alignas(4) float padding3; } fog; struct Bloom { alignas(4) float exposure; alignas(4) float gamma; alignas(4) float threshold; alignas(4) uint32_t padding2; } bloom; struct VXGI { alignas(16) pod::Matrix4f matrix; alignas(4) float cascadePower; alignas(4) float granularity; alignas(4) float voxelizeScale; alignas(4) float occlusionFalloff; alignas(4) float traceStartOffsetFactor; alignas(4) uint32_t shadows; alignas(4) uint32_t padding2; alignas(4) uint32_t padding3; } vxgi; struct RT { alignas(8) pod::Vector2f defaultRayBounds; alignas(4) float alphaTestOffset; alignas(4) float padding1; alignas(4) uint samples; alignas(4) uint paths; alignas(4) uint frameAccumulationMinimum; alignas(4) uint padding2; } rt; } settings; }; // struct that contains our skybox cubemap, noise texture, and VXGI voxels auto& sceneTextures = this->getComponent(); uf::stl::vector textures2D; textures2D.reserve( metadata.max.textures2D ); uf::stl::vector textures3D; textures3D.reserve( metadata.max.textures3D ); uf::stl::vector texturesCube; texturesCube.reserve( metadata.max.texturesCube ); // bind scene textures for ( auto& key : uf::graph::storage.texture2Ds.keys ) textures2D.emplace_back().aliasTexture( uf::graph::storage.texture2Ds.map[key] ); // bind shadow maps for ( auto& texture : uf::graph::storage.shadow2Ds ) textures2D.emplace_back().aliasTexture(texture); for ( auto& texture : uf::graph::storage.shadowCubes ) texturesCube.emplace_back().aliasTexture(texture); // bind skybox size_t indexSkybox = texturesCube.size(); texturesCube.emplace_back().aliasTexture(sceneTextures.skybox); // bind noise texture size_t indexNoise = textures3D.size(); textures3D.emplace_back().aliasTexture(sceneTextures.noise); // attach VXGI voxels if ( uf::renderer::settings::pipelines::vxgi ) { for ( auto& t : sceneTextures.voxels.id ) textures3D.emplace_back().aliasTexture(t); for ( auto& t : sceneTextures.voxels.normal ) textures3D.emplace_back().aliasTexture(t); for ( auto& t : sceneTextures.voxels.uv ) textures3D.emplace_back().aliasTexture(t); for ( auto& t : sceneTextures.voxels.radiance ) textures3D.emplace_back().aliasTexture(t); for ( auto& t : sceneTextures.voxels.depth ) textures3D.emplace_back().aliasTexture(t); } // bind textures while ( textures2D.size() < metadata.max.textures2D ) textures2D.emplace_back().aliasTexture(uf::renderer::Texture2D::empty); while ( texturesCube.size() < metadata.max.texturesCube ) texturesCube.emplace_back().aliasTexture(uf::renderer::TextureCube::empty); while ( textures3D.size() < metadata.max.textures3D ) textures3D.emplace_back().aliasTexture(uf::renderer::Texture3D::empty); static uf::stl::unordered_map previousUniformsMap; auto& previousUniforms = previousUniformsMap[shaderType+":"+shaderPipeline+":"+std::to_string(&graphic)]; // update uniform information // hopefully write combining kicks in UniformDescriptor uniforms; { for ( auto i = 0; i < 2; ++i ) { #if UF_USE_FFX_FSR auto projection = ext::fsr::getJitterMatrix() * camera.getProjection(i); #else auto projection = camera.getProjection(i); #endif uniforms.matrices[i] = UniformDescriptor::Matrices{ .view = camera.getView(i), .projection = projection, .model = projection * camera.getView(i), .previous = previousUniforms.matrices[i].model, .iView = uf::matrix::inverse( camera.getView(i) ), .iProjection = uf::matrix::inverse( projection ), // .iProjectionView = uf::matrix::inverse( projection * camera.getView(i) ), .eyePos = camera.getEye( i ), }; } uniforms.settings.lengths = UniformDescriptor::Settings::Lengths{ .lights = MIN( uf::graph::storage.lights.size(), metadata.light.max ), .materials = MIN( uf::graph::storage.materials.keys.size(), metadata.max.textures2D ), .textures = MIN( uf::graph::storage.textures.keys.size(), metadata.max.textures2D ), .drawCommands = MIN( 0, metadata.max.textures2D ), }; uniforms.settings.mode = UniformDescriptor::Settings::Mode{ .parameters = metadata.shader.parameters, .mode = metadata.shader.mode, .scalar = metadata.shader.scalar, .msaa = ext::vulkan::settings::msaa, .frameAccumulate = metadata.shader.frameAccumulate, }; uniforms.settings.lighting = UniformDescriptor::Settings::Lighting{ .ambient = metadata.light.ambient, .indexSkybox = indexSkybox, .maxShadows = metadata.shadow.max, .shadowSamples = std::min( 0, metadata.shadow.samples ), .useLightmaps = metadata.light.useLightmaps, }; uniforms.settings.fog = UniformDescriptor::Settings::Fog{ .color = metadata.fog.color, .stepScale = metadata.fog.stepScale, .offset = metadata.fog.density.offset * (float) metadata.fog.density.timescale * (float) uf::physics::time::current, .densityScale = metadata.fog.density.scale, .range = metadata.fog.range, .densityThreshold = metadata.fog.density.threshold, .densityMultiplier = metadata.fog.density.multiplier, .absorbtion = metadata.fog.absorbtion, }; uniforms.settings.bloom = UniformDescriptor::Settings::Bloom{ .exposure = metadata.light.exposure, .gamma = metadata.light.gamma, .threshold = metadata.bloom.threshold, }; uniforms.settings.vxgi = UniformDescriptor::Settings::VXGI{ .matrix = metadataVxgi.extents.matrix, .cascadePower = metadataVxgi.cascadePower, .granularity = metadataVxgi.granularity, .voxelizeScale = 1.0f / (metadataVxgi.voxelizeScale * std::max( metadataVxgi.voxelSize.x, std::max(metadataVxgi.voxelSize.y, metadataVxgi.voxelSize.z))), .occlusionFalloff = metadataVxgi.occlusionFalloff, .traceStartOffsetFactor = metadataVxgi.traceStartOffsetFactor, .shadows = metadataVxgi.shadows, }; uniforms.settings.rt = UniformDescriptor::Settings::RT{ .defaultRayBounds = metadataRt.settings.defaultRayBounds, // { 0.001, 4096.0 }, .alphaTestOffset = metadataRt.settings.alphaTestOffset, //0.001, .samples = metadataRt.settings.samples, // 1, .paths = metadataRt.settings.paths, // 1, .frameAccumulationMinimum = metadataRt.settings.frameAccumulationMinimum, // 0, }; } uf::stl::vector previousTextures; for ( auto& texture : graphic.material.textures ) previousTextures.emplace_back(texture.image); graphic.material.textures.clear(); // attach our textures to the graphic for ( auto& t : textures2D ) graphic.material.textures.emplace_back().aliasTexture(t); for ( auto& t : texturesCube ) graphic.material.textures.emplace_back().aliasTexture(t); for ( auto& t : textures3D ) graphic.material.textures.emplace_back().aliasTexture(t); // trigger an update when we have differing bound texture sizes bool shouldUpdate = metadata.shader.invalidated || graphic.material.textures.size() != previousTextures.size(); for ( uint32_t i = 0; !shouldUpdate && i < previousTextures.size() && i < graphic.material.textures.size(); ++i ) { if ( previousTextures[i] != graphic.material.textures[i].image ) shouldUpdate = true; } if ( shouldUpdate ) { graphic.updatePipelines(); metadata.shader.invalidated = false; } auto& shader = graphic.material.getShader(shaderType, shaderPipeline); shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") ); bool shouldUpdate2 = !uf::matrix::equals( uniforms.matrices[0].view, previousUniforms.matrices[0].view, 0.0001f ); if ( shouldUpdate || shouldUpdate2 ) { metadata.shader.frameAccumulateReset = true; uf::renderer::states::frameAccumulateReset = true; previousUniforms = uniforms; } #endif } #undef this