mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
336 lines
11 KiB
C++
336 lines
11 KiB
C++
#include "stdafx.h"
|
|
#include "VisualEngine.h"
|
|
|
|
#include "GfxBase/RenderCaps.h"
|
|
#include "GfxBase/RenderStats.h"
|
|
#include "GfxBase/FrameRateManager.h"
|
|
|
|
#include "GfxCore/Device.h"
|
|
|
|
#include "GlobalShaderData.h"
|
|
#include "ShaderManager.h"
|
|
#include "TextureManager.h"
|
|
#include "TextureCompositor.h"
|
|
#include "LightGrid.h"
|
|
#include "Water.h"
|
|
#include "EmitterShared.h"
|
|
#include "MaterialGenerator.h"
|
|
#include "SceneManager.h"
|
|
#include "TypesetterBitmap.h"
|
|
#include "TypesetterDynamic.h"
|
|
#include "AdornRender.h"
|
|
#include "VertexStreamer.h"
|
|
#include "SceneUpdater.h"
|
|
#include "SmoothCluster.h"
|
|
#include "TextureAtlas.h"
|
|
|
|
#include "GfxBase/Typesetter.h"
|
|
|
|
#include "v8datamodel/DataModel.h"
|
|
#include "v8datamodel/ContentProvider.h"
|
|
#include "v8datamodel/MeshContentProvider.h"
|
|
#include "v8datamodel/SolidModelContentProvider.h"
|
|
#include "v8datamodel/Camera.h"
|
|
#include "v8datamodel/MegaCluster.h"
|
|
#include "v8datamodel/Workspace.h"
|
|
#include "v8datamodel/Lighting.h"
|
|
#include "rbx/SystemUtil.h"
|
|
|
|
LOGGROUP(Graphics)
|
|
|
|
FASTFLAGVARIABLE(CancelPendingTextureLoads, true)
|
|
FASTFLAG(UseDynamicTypesetterUTF8)
|
|
|
|
FASTFLAG(CameraVR)
|
|
|
|
namespace RBX
|
|
{
|
|
namespace Graphics
|
|
{
|
|
|
|
VisualEngine::VisualEngine(Device* device, CRenderSettings* settings)
|
|
: device(device)
|
|
, viewWidth(0)
|
|
, viewHeight(0)
|
|
, settings(settings)
|
|
, contentProvider(0)
|
|
, lighting(0)
|
|
, meshContentProvider(0)
|
|
{
|
|
renderStats.reset(new RenderStats());
|
|
renderCaps.reset(new RenderCaps("Unknown", SystemUtil::getVideoMemory()));
|
|
|
|
FASTLOG1(FLog::Graphics, "Video memory size: %lld", SystemUtil::getVideoMemory());
|
|
|
|
GlobalShaderData::define(device);
|
|
|
|
// load shaders
|
|
shaderManager.reset(new ShaderManager(this));
|
|
shaderManager->loadShaders(ContentProvider::assetFolder() + "../shaders", device->getShadingLanguage(), /* consoleOutput= */ false);
|
|
|
|
// initialize texture manager
|
|
textureManager.reset(new TextureManager(this));
|
|
|
|
// create light grid
|
|
LightGrid::TextureMode gridTextureMode =
|
|
device->getShadingLanguage() == "glsles"
|
|
? LightGrid::Texture_2D
|
|
:
|
|
(device->getCaps().supportsShaders && device->getCaps().supportsTexture3D)
|
|
? LightGrid::Texture_3D
|
|
: LightGrid::Texture_None;
|
|
|
|
// Note: if there is no texture support we still create a 4x4x4 texture
|
|
// We would really like the height to be constant since anything above the height does not cast shadows, so this affects ceiling heights
|
|
// Having XZ size of 2x2 does not really let us have an efficient sliding window - a character is often too close to one of the edges
|
|
// 3x3 is an option, but it's easier to go with 4x4 for now.
|
|
Vector3int32 gridSize =
|
|
(gridTextureMode != LightGrid::Texture_None && renderCaps->getVidMemSize() > 100*1024*1024)
|
|
? Vector3int32(8, 4, 8) // 8x4x8 chunks take 16 Mb of VRAM
|
|
: Vector3int32(4, 4, 4);
|
|
|
|
LightGrid* lgrid = LightGrid::create(this, gridSize, gridTextureMode);
|
|
RBXASSERT(lgrid);
|
|
|
|
lightGrid.reset(lgrid);
|
|
|
|
// load fonts
|
|
if (FFlag::UseDynamicTypesetterUTF8)
|
|
{
|
|
glyphAtlas.reset(new TextureAtlas(this, 2048, 2048));
|
|
for (Text::Font font = Text::FONT_LEGACY; font != Text::FONT_LAST; font=Text::Font(font+1))
|
|
{
|
|
static const char* kFontTTFPaths[] = {
|
|
"fonts/arial.ttf", "fonts/arial.ttf", "fonts/arialbd.ttf", "fonts/SourceSansPro-Regular.ttf", "fonts/SourceSansPro-Bold.ttf", "fonts/SourceSansPro-Light.ttf", "fonts/SourceSansPro-It.ttf"
|
|
};
|
|
|
|
float legacyHeightScale = (font == Text::FONT_LEGACY) ? 1.5f : 1.f;
|
|
typesetters[font].reset(new TypesetterDynamic(glyphAtlas.get(), textureManager.get(), ContentProvider::assetFolder() + kFontTTFPaths[font], legacyHeightScale, (unsigned)font, device->getCaps().retina));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (Text::Font font = Text::FONT_LEGACY; font != Text::FONT_LAST; font=Text::Font(font+1))
|
|
{
|
|
static const char* kFontPaths[Text::FONT_LAST] =
|
|
{
|
|
"fonts/Arial.font", "fonts/Arial.font", "fonts/ArialBold.font", "fonts/SourceSans.font", "fonts/SourceSansBold.font",
|
|
"fonts/SourceSansLight.font", "fonts/SourceSansItalic.font",
|
|
};
|
|
|
|
static const char* kTexturePath = "rbxasset://fonts/fonts.dds";
|
|
|
|
float legacyHeightScale = (font == Text::FONT_LEGACY) ? 1.5f : 1.f;
|
|
typesetters[font].reset(new TypesetterBitmap(getTextureManager(), ContentProvider::assetFolder() + kFontPaths[font], kTexturePath, legacyHeightScale, device->getCaps().retina));
|
|
}
|
|
}
|
|
|
|
materialGenerator.reset(new MaterialGenerator(this));
|
|
|
|
sceneManager.reset(new SceneManager(this));
|
|
|
|
frameRateManager.reset(new FrameRateManager());
|
|
|
|
vertexStreamer.reset(new VertexStreamer(this));
|
|
|
|
textureCompositor.reset(new TextureCompositor(this));
|
|
|
|
water.reset(new Water(this));
|
|
|
|
emitterShared.reset( new EmitterShared );
|
|
|
|
// set up caps
|
|
bool gbufferSupported =
|
|
shaderManager->getProgram("PassThroughVS", "SSAOFS").get() != NULL &&
|
|
device->getCaps().maxDrawBuffers >= 2 &&
|
|
(device->getFeatureLevel() == "D3D11" || SystemUtil::getVideoMemory() >= 128*1024*1024);
|
|
|
|
renderCaps->setSupportsGBuffer(gbufferSupported);
|
|
|
|
if (shared_ptr<ShaderProgram> program = shaderManager->getProgram("DefaultSkinnedVS", "DefaultFS"))
|
|
{
|
|
unsigned int boneCount = program->getMaxWorldTransforms();
|
|
|
|
FASTLOG1(FLog::Graphics, "Supported bones for skinning: %d", boneCount);
|
|
|
|
renderCaps->setSkinningBoneCount(boneCount);
|
|
}
|
|
else
|
|
{
|
|
FASTLOG(FLog::Graphics, "Supported bones for skinning: 0 (no shader support)");
|
|
}
|
|
|
|
// configure FRM after caps are valid
|
|
frameRateManager->Configure(renderCaps.get(), settings);
|
|
}
|
|
|
|
const shared_ptr<Typesetter>& VisualEngine::getTypesetter(Text::Font font)
|
|
{
|
|
return typesetters[font];
|
|
}
|
|
|
|
VisualEngine::~VisualEngine()
|
|
{
|
|
bindWorkspace(shared_ptr<DataModel>());
|
|
}
|
|
|
|
void VisualEngine::bindWorkspace(const shared_ptr<DataModel>& dm)
|
|
{
|
|
if (dm)
|
|
{
|
|
contentProvider = ServiceProvider::create<ContentProvider>(dm.get());
|
|
meshContentProvider = ServiceProvider::create<MeshContentProvider>(dm.get());
|
|
lighting = ServiceProvider::create<Lighting>(dm.get());
|
|
meshContentProvider->setCacheSize(settings->getMeshCacheSize());
|
|
ServiceProvider::create<SolidModelContentProvider>(dm.get());
|
|
|
|
RBXASSERT(!sceneUpdater);
|
|
sceneUpdater.reset(new SceneUpdater(dm, this));
|
|
|
|
adorn.reset(new AdornRender(this, dm.get()));
|
|
|
|
if (lightGrid)
|
|
{
|
|
// Clear the grid and do an initial upload of all chunks to ensure texture has correct data
|
|
lightGrid->lightingClearAll();
|
|
lightGrid->lightingUploadAll();
|
|
lightGrid->lightingUploadCommit();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
contentProvider = NULL;
|
|
meshContentProvider = NULL;
|
|
lighting = NULL;
|
|
|
|
if (sceneUpdater)
|
|
{
|
|
sceneUpdater->unbind();
|
|
sceneUpdater.reset();
|
|
}
|
|
|
|
adorn.reset();
|
|
|
|
if (FFlag::CancelPendingTextureLoads)
|
|
{
|
|
textureCompositor->cancelPendingRequests();
|
|
textureManager->cancelPendingRequests();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisualEngine::setViewport(int width, int height)
|
|
{
|
|
viewWidth = width;
|
|
viewHeight = height;
|
|
}
|
|
|
|
void VisualEngine::setCamera(const Camera& value, const G3D::Vector3& poi)
|
|
{
|
|
if (FFlag::CameraVR)
|
|
camera.setViewCFrame(value.getRenderingCoordinateFrame());
|
|
else
|
|
camera.setViewCFrame(value.coordinateFrame(), value.getRoll());
|
|
|
|
camera.setProjectionPerspective(value.getFieldOfView(), static_cast<float>(viewWidth) / viewHeight, -value.nearPlaneZ(), -value.farPlaneZ());
|
|
|
|
sceneManager->setPointOfInterest(poi);
|
|
|
|
if (sceneUpdater)
|
|
sceneUpdater->setPointOfInterest(poi);
|
|
|
|
// get frustum with the far clip plane to max block distance
|
|
float updateFarPlaneZ;
|
|
if (frameRateManager) {
|
|
updateFarPlaneZ = (float) -std::max(1.0, frameRateManager->GetMaxNextViewCullDistance());
|
|
updateFarPlaneZ = std::max(updateFarPlaneZ, value.farPlaneZ());
|
|
} else {
|
|
updateFarPlaneZ = value.farPlaneZ();
|
|
}
|
|
|
|
value.frustum(updateFarPlaneZ, updateFrustum);
|
|
|
|
// update camera for culling
|
|
cameraCull = camera;
|
|
|
|
if (DeviceVR* vr = device->getVR())
|
|
{
|
|
DeviceVR::State vrState = vr->getState();
|
|
|
|
// Use max FOV for culling; ideally we also should account for IPD
|
|
cameraCull.setProjectionPerspective(
|
|
std::max(vrState.eyeFov[0][0], vrState.eyeFov[1][0]),
|
|
std::max(vrState.eyeFov[0][1], vrState.eyeFov[1][1]),
|
|
std::max(vrState.eyeFov[0][2], vrState.eyeFov[1][2]),
|
|
std::max(vrState.eyeFov[0][3], vrState.eyeFov[1][3]),
|
|
-value.nearPlaneZ(), -value.farPlaneZ());
|
|
}
|
|
|
|
// update camera for FRM culling
|
|
cameraCullFrm = cameraCull;
|
|
|
|
cameraCullFrm.changeProjectionPerspectiveZ(-value.nearPlaneZ(), std::min(-value.farPlaneZ(), sqrtf(frameRateManager->GetRenderCullSqDistance())));
|
|
}
|
|
|
|
void VisualEngine::reloadShaders()
|
|
{
|
|
shaderManager->loadShaders(ContentProvider::assetFolder() + "../shaders", device->getShadingLanguage(), /* consoleOutput= */ true);
|
|
}
|
|
|
|
void VisualEngine::reloadQueuedAssets()
|
|
{
|
|
for (FilenameCountdown::iterator it = assetsToReload.begin(); it != assetsToReload.end(); ++it)
|
|
{
|
|
--it->second;
|
|
|
|
if (it->second == 0)
|
|
{
|
|
const std::string& filePath = it->first;
|
|
immediateAssetReload(filePath);
|
|
assetsToReload.erase(it);
|
|
|
|
break; // one reload per frame is enough
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisualEngine::queueAssetReload(const std::string& filePath)
|
|
{
|
|
if (!filePath.empty())
|
|
assetsToReload[filePath] = 2; // wait this amount of frames before reloading
|
|
}
|
|
|
|
static void reloadMaterialTable(MegaClusterInstance* mci)
|
|
{
|
|
mci->reloadMaterialTable();
|
|
|
|
if (SmoothClusterBase* sc = dynamic_cast<SmoothClusterBase*>(mci->getGfxPart()))
|
|
sc->reloadMaterialTable();
|
|
}
|
|
|
|
void VisualEngine::immediateAssetReload(const std::string& filePath)
|
|
{
|
|
if (filePath.find("rbxasset://") == 0 || filePath.find("rbxgameasset://") == 0 || filePath.find("rbxapp://") == 0)
|
|
{
|
|
if (filePath == "rbxasset://terrain/materials.json")
|
|
{
|
|
if (MegaClusterInstance* mci = Instance::fastDynamicCast<MegaClusterInstance>(sceneUpdater->getDataModel()->getWorkspace()->getTerrain()))
|
|
DataModel::get(mci)->submitTask(boost::bind(reloadMaterialTable, mci), DataModelJob::Write);
|
|
}
|
|
|
|
getTextureManager()->reloadImage(ContentId(filePath));
|
|
}
|
|
else
|
|
{
|
|
std::string extension = filePath.substr(std::min(filePath.find_last_of(".") + 1, filePath.size()));
|
|
if (extension == "hlsl" || extension == "h")
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "Reloading shaders");
|
|
reloadShaders();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|