pioneer/src/GasGiant.cpp

872 lines
27 KiB
C++

// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "GasGiant.h"
#include "FileSystem.h"
#include "Game.h"
#include "GameConfig.h"
#include "Pi.h"
#include "galaxy/AtmosphereParameters.h"
#include "graphics/Frustum.h"
#include "graphics/Graphics.h"
#include "graphics/Material.h"
#include "graphics/RenderState.h"
#include "graphics/Renderer.h"
#include "graphics/Texture.h"
#include "graphics/VertexArray.h"
#include "graphics/opengl/GenGasGiantColourMaterial.h"
#include "perlin.h"
#include "utils.h"
#include "vcacheopt/vcacheopt.h"
RefCountedPtr<GasPatchContext> GasGiant::s_patchContext;
namespace {
static Uint32 s_texture_size_small = 16;
static Uint32 s_texture_size_cpu[5];
static Uint32 s_texture_size_gpu[5];
static Uint32 s_noiseOctaves[5];
static float s_initialCPUDelayTime = 60.0f; // (perhaps) 60 seconds seems like a reasonable default
static float s_initialGPUDelayTime = 5.0f; // (perhaps) 5 seconds seems like a reasonable default
static std::vector<GasGiant *> s_allGasGiants;
static const std::string GGJupiter("GGJupiter");
static const std::string GGNeptune("GGNeptune");
static const std::string GGNeptune2("GGNeptune2");
static const std::string GGSaturn("GGSaturn");
static const std::string GGSaturn2("GGSaturn2");
static const std::string GGUranus("GGUranus");
bool SplitData(const std::string &spec, Uint32 &cpuOut, Uint32 &gpuOut, Uint32 &octavesOut)
{
static const std::string delim(",");
enum dataEntries {
eCPU = 0,
eGPU,
eOCTAVES
};
size_t i = 0, start = 0, end = 0;
while (end != std::string::npos) {
// get to the first non-delim char
start = spec.find_first_not_of(delim, end);
// read the end, no more to do
if (start == std::string::npos)
break;
// find the end - next delim or end of string
end = spec.find_first_of(delim, start);
// extract the fragment and remember it
switch (i) {
case eCPU:
cpuOut = ceil_pow2(Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 64, 4096));
break;
case eGPU:
gpuOut = ceil_pow2(Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 64, 4096));
break;
case eOCTAVES:
octavesOut = Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 1, 16);
break;
default:
assert(false);
break;
}
i++;
}
return i == 4;
}
} // namespace
class GasPatchContext : public RefCounted {
public:
#pragma pack(push, 4)
struct VBOVertex {
vector3f pos;
vector3f norm;
};
#pragma pack(pop)
int edgeLen;
inline int IDX_VBO_COUNT_ALL_IDX() const { return ((edgeLen - 1) * (edgeLen - 1)) * 2 * 3; }
inline int NUMVERTICES() const { return edgeLen * edgeLen; }
double frac;
std::unique_ptr<Uint32[]> indices;
RefCountedPtr<Graphics::IndexBuffer> indexBuffer;
GasPatchContext(const int _edgeLen) :
edgeLen(_edgeLen)
{
Init();
}
~GasPatchContext()
{
Cleanup();
}
void Refresh()
{
Cleanup();
Init();
}
void Cleanup()
{
indices.reset();
}
int GetIndices(std::vector<Uint32> &pl)
{
// calculate how many tri's there are
const int tri_count = IDX_VBO_COUNT_ALL_IDX() / 3;
// pre-allocate enough space
pl.reserve(IDX_VBO_COUNT_ALL_IDX());
// add all of the middle indices
for (int i = 0; i < IDX_VBO_COUNT_ALL_IDX(); ++i) {
pl.push_back(indices[i]);
}
return tri_count;
}
void Init()
{
PROFILE_SCOPED()
frac = 1.0 / double(edgeLen - 1);
// also want vtx indices for tris not touching edge of patch
indices.reset(new Uint32[IDX_VBO_COUNT_ALL_IDX()]);
Uint32 *idx = indices.get();
for (int x = 0; x < edgeLen - 1; x++) {
for (int y = 0; y < edgeLen - 1; y++) {
idx[0] = x + edgeLen * y;
idx[1] = x + 1 + edgeLen * y;
idx[2] = x + edgeLen * (y + 1);
idx += 3;
idx[0] = x + 1 + edgeLen * y;
idx[1] = x + 1 + edgeLen * (y + 1);
idx[2] = x + edgeLen * (y + 1);
idx += 3;
}
}
// these will hold the optimised indices
std::vector<Uint32> pl_short;
// populate the N indices lists from the arrays built during InitTerrainIndices()
// iterate over each index list and optimize it
Uint32 tri_count = GetIndices(pl_short);
VertexCacheOptimizerUInt vco;
#ifndef NDEBUG
VertexCacheOptimizerUInt::Result res = vco.Optimize(&pl_short[0], tri_count);
assert(0 == res);
#else
vco.Optimize(&pl_short[0], tri_count);
#endif
//create buffer & copy
indexBuffer.Reset(Pi::renderer->CreateIndexBuffer(pl_short.size(), Graphics::BUFFER_USAGE_STATIC));
Uint32 *idxPtr = indexBuffer->Map(Graphics::BUFFER_MAP_WRITE);
for (Uint32 j = 0; j < pl_short.size(); j++) {
idxPtr[j] = pl_short[j];
}
indexBuffer->Unmap();
if (indices) {
indices.reset();
}
}
};
class GasPatch {
public:
RefCountedPtr<GasPatchContext> ctx;
vector3d v[4];
std::unique_ptr<Graphics::VertexBuffer> m_vertexBuffer;
GasGiant *gasSphere;
vector3d clipCentroid;
double clipRadius;
GasPatch(const RefCountedPtr<GasPatchContext> &_ctx, GasGiant *gs, vector3d v0, vector3d v1, vector3d v2, vector3d v3) :
ctx(_ctx),
gasSphere(gs),
clipCentroid(((v0 + v1 + v2 + v3) * 0.25).Normalized()),
clipRadius(0.0)
{
PROFILE_SCOPED()
v[0] = v0;
v[1] = v1;
v[2] = v2;
v[3] = v3;
for (int i = 0; i < 4; i++) {
clipRadius = std::max(clipRadius, (v[i] - clipCentroid).Length());
}
UpdateVBOs();
}
~GasPatch() {}
/* in patch surface coords, [0,1] */
vector3d GetSpherePoint(const double x, const double y) const
{
return (v[0] + x * (1.0 - y) * (v[1] - v[0]) + x * y * (v[2] - v[0]) + (1.0 - x) * y * (v[3] - v[0])).Normalized();
}
void UpdateVBOs()
{
PROFILE_SCOPED()
//create buffer and upload data
Graphics::VertexBufferDesc vbd;
vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
vbd.attrib[1].semantic = Graphics::ATTRIB_NORMAL;
vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_FLOAT3;
vbd.numVertices = ctx->NUMVERTICES();
vbd.usage = Graphics::BUFFER_USAGE_STATIC;
m_vertexBuffer.reset(Pi::renderer->CreateVertexBuffer(vbd));
GasPatchContext::VBOVertex *vtxPtr = m_vertexBuffer->Map<GasPatchContext::VBOVertex>(Graphics::BUFFER_MAP_WRITE);
assert(m_vertexBuffer->GetDesc().stride == sizeof(GasPatchContext::VBOVertex));
const Sint32 edgeLen = ctx->edgeLen;
const double frac = ctx->frac;
for (Sint32 y = 0; y < edgeLen; y++) {
for (Sint32 x = 0; x < edgeLen; x++) {
const vector3d p = GetSpherePoint(x * frac, y * frac);
const vector3d pSubCentroid = p - clipCentroid;
clipRadius = std::max(clipRadius, p.Length());
vtxPtr->pos = vector3f(pSubCentroid);
vtxPtr->norm = vector3f(p);
++vtxPtr; // next vertex
}
}
m_vertexBuffer->Unmap();
}
void Render(Graphics::Renderer *renderer, const vector3d &campos, const matrix4x4d &modelView, const Graphics::Frustum &frustum)
{
if (!frustum.TestPoint(clipCentroid, clipRadius))
return;
RefCountedPtr<Graphics::Material> mat = gasSphere->GetSurfaceMaterial();
Graphics::RenderState *rs = gasSphere->GetSurfRenderState();
const vector3d relpos = clipCentroid - campos;
renderer->SetTransform(matrix4x4f(modelView * matrix4x4d::Translation(relpos)));
Pi::statSceneTris += 2 * (ctx->edgeLen - 1) * (ctx->edgeLen - 1);
++Pi::statNumPatches;
renderer->DrawBufferIndexed(m_vertexBuffer.get(), ctx->indexBuffer.Get(), rs, mat.Get());
renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_PATCHES, 1);
}
};
Graphics::RenderTarget *GasGiant::s_renderTarget;
Graphics::RenderState *GasGiant::s_quadRenderState;
// static
void GasGiant::UpdateAllGasGiants()
{
PROFILE_SCOPED()
for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(); i != s_allGasGiants.end(); ++i) {
(*i)->Update();
}
}
// static
void GasGiant::OnChangeDetailLevel()
{
s_patchContext.Reset(new GasPatchContext(127));
// reinit the geosphere terrain data
for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(); i != s_allGasGiants.end(); ++i) {
// clearout anything we don't need
(*i)->Reset();
// reinit the terrain with the new settings
(*i)->m_terrain.Reset(Terrain::InstanceTerrain((*i)->GetSystemBody()));
}
}
GasGiant::GasGiant(const SystemBody *body) :
BaseSphere(body),
m_hasTempCampos(false),
m_tempCampos(0.0),
m_hasGpuJobRequest(false),
m_timeDelay(s_initialCPUDelayTime)
{
s_allGasGiants.push_back(this);
for (int i = 0; i < NUM_PATCHES; i++) {
m_hasJobRequest[i] = false;
}
Random rng(GetSystemBody()->GetSeed() + 4609837);
const bool bEnableGPUJobs = (Pi::config->Int("EnableGPUJobs") == 1);
if (bEnableGPUJobs)
m_timeDelay = s_initialGPUDelayTime + (rng.Double() * (s_initialGPUDelayTime * 0.5));
//SetUpMaterials is not called until first Render since light count is zero :)
//BuildFirstPatches and GenerateTexture are only called when we first attempt to render
}
GasGiant::~GasGiant()
{
// update thread should not be able to access us now, so we can safely continue to delete
assert(std::count(s_allGasGiants.begin(), s_allGasGiants.end(), this) == 1);
s_allGasGiants.erase(std::find(s_allGasGiants.begin(), s_allGasGiants.end(), this));
}
void GasGiant::Reset()
{
{
for (int i = 0; i < NUM_PATCHES; i++) {
if (m_hasJobRequest[i] && m_job[i].HasJob())
m_job[i].GetJob()->OnCancel();
m_hasJobRequest[i] = false;
}
}
for (int p = 0; p < NUM_PATCHES; p++) {
// delete patches
if (m_patches[p]) {
m_patches[p].reset();
}
}
m_surfaceTextureSmall.Reset();
m_surfaceTexture.Reset();
m_surfaceMaterial.Reset();
}
//static
bool GasGiant::OnAddTextureFaceResult(const SystemPath &path, GasGiantJobs::STextureFaceResult *res)
{
// Find the correct GeoSphere via it's system path, and give it the split result
for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(), iEnd = s_allGasGiants.end(); i != iEnd; ++i) {
if (path == (*i)->GetSystemBody()->GetPath()) {
(*i)->AddTextureFaceResult(res);
return true;
}
}
// GasGiant not found to return the data to, cancel (which deletes it) instead
if (res) {
res->OnCancel();
delete res;
}
return false;
}
//static
bool GasGiant::OnAddGPUGenResult(const SystemPath &path, GasGiantJobs::SGPUGenResult *res)
{
// Find the correct GeoSphere via it's system path, and give it the split result
for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(), iEnd = s_allGasGiants.end(); i != iEnd; ++i) {
if (path == (*i)->GetSystemBody()->GetPath()) {
(*i)->AddGPUGenResult(res);
return true;
}
}
// GasGiant not found to return the data to, cancel (which deletes it) instead
if (res) {
res->OnCancel();
delete res;
}
return false;
}
#define DUMP_TO_TEXTURE 0
#if DUMP_TO_TEXTURE
#include "FileSystem.h"
#include "PngWriter.h"
#include "graphics/opengl/TextureGL.h"
void textureDump(const char *destFile, const int width, const int height, const Color *buf)
{
const std::string dir = "generated_textures";
FileSystem::userFiles.MakeDirectory(dir);
const std::string fname = FileSystem::JoinPathBelow(dir, destFile);
// pad rows to 4 bytes, which is the default row alignment for OpenGL
//const int stride = (3*width + 3) & ~3;
const int stride = width * 4;
write_png(FileSystem::userFiles, fname, &buf[0].r, width, height, stride, 4);
printf("texture %s saved\n", fname.c_str());
}
#endif
bool GasGiant::AddTextureFaceResult(GasGiantJobs::STextureFaceResult *res)
{
bool result = false;
assert(res);
assert(res->face() >= 0 && res->face() < NUM_PATCHES);
m_jobColorBuffers[res->face()].reset(res->data().colors);
m_hasJobRequest[res->face()] = false;
const Sint32 uvDims = res->data().uvDims;
assert(uvDims > 0 && uvDims <= 4096);
// tidyup
delete res;
bool bCreateTexture = true;
for (int i = 0; i < NUM_PATCHES; i++) {
bCreateTexture = bCreateTexture & (!m_hasJobRequest[i]);
}
if (bCreateTexture) {
// create texture
const vector2f texSize(1.0f, 1.0f);
const vector3f dataSize(uvDims, uvDims, 0.0f);
const Graphics::TextureDescriptor texDesc(
Graphics::TEXTURE_RGBA_8888,
dataSize, texSize, Graphics::LINEAR_CLAMP,
true, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
m_surfaceTexture.Reset(Pi::renderer->CreateTexture(texDesc));
// update with buffer from above
Graphics::TextureCubeData tcd;
tcd.posX = m_jobColorBuffers[0].get();
tcd.negX = m_jobColorBuffers[1].get();
tcd.posY = m_jobColorBuffers[2].get();
tcd.negY = m_jobColorBuffers[3].get();
tcd.posZ = m_jobColorBuffers[4].get();
tcd.negZ = m_jobColorBuffers[5].get();
m_surfaceTexture->Update(tcd, dataSize, Graphics::TEXTURE_RGBA_8888);
#if DUMP_TO_TEXTURE
for (int iFace = 0; iFace < NUM_PATCHES; iFace++) {
char filename[1024];
snprintf(filename, 1024, "%s%d.png", GetSystemBody()->GetName().c_str(), iFace);
textureDump(filename, uvDims, uvDims, m_jobColorBuffers[iFace].get());
}
#endif
// cleanup the temporary color buffer storage
for (int i = 0; i < NUM_PATCHES; i++) {
m_jobColorBuffers[i].reset();
}
// change the planet texture for the new higher resolution texture
if (m_surfaceMaterial.Get()) {
m_surfaceMaterial->texture0 = m_surfaceTexture.Get();
m_surfaceTextureSmall.Reset();
}
}
return result;
}
bool GasGiant::AddGPUGenResult(GasGiantJobs::SGPUGenResult *res)
{
bool result = false;
assert(res);
m_hasGpuJobRequest = false;
assert(!m_gpuJob.HasJob());
#ifndef NDEBUG
const Sint32 uvDims = res->data().uvDims;
assert(uvDims > 0 && uvDims <= 4096);
#endif
#if DUMP_TO_TEXTURE
for (int iFace = 0; iFace < NUM_PATCHES; iFace++) {
std::unique_ptr<Color, FreeDeleter> buffer(static_cast<Color *>(malloc(uvDims * uvDims * 4)));
Graphics::Texture *pTex = res->data().texture.Get();
Graphics::TextureGL *pGLTex = static_cast<Graphics::TextureGL *>(pTex);
pGLTex->Bind();
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + iFace, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.get());
pGLTex->Unbind();
char filename[1024];
snprintf(filename, 1024, "%s%d.png", GetSystemBody()->GetName().c_str(), iFace);
textureDump(filename, uvDims, uvDims, buffer.get());
}
#endif
// tidyup
delete res;
if (m_builtTexture.Valid()) {
m_surfaceTexture = m_builtTexture;
m_builtTexture.Reset();
// these won't be automatically generated otherwise since we used it as a render target
m_surfaceTexture->BuildMipmaps();
// change the planet texture for the new higher resolution texture
if (m_surfaceMaterial.Get()) {
m_surfaceMaterial->texture0 = m_surfaceTexture.Get();
m_surfaceTextureSmall.Reset();
}
}
return result;
}
// in patch surface coords, [0,1]
inline vector3d GetSpherePointFromCorners(const double x, const double y, const vector3d *corners)
{
return (corners[0] + x * (1.0 - y) * (corners[1] - corners[0]) + x * y * (corners[2] - corners[0]) + (1.0 - x) * y * (corners[3] - corners[0])).Normalized();
}
void GasGiant::GenerateTexture()
{
using namespace GasGiantJobs;
for (int i = 0; i < NUM_PATCHES; i++) {
if (m_hasGpuJobRequest || m_hasJobRequest[i])
return;
}
const bool bEnableGPUJobs = (Pi::config->Int("EnableGPUJobs") == 1);
// scope the small texture generation
{
const vector2f texSize(1.0f, 1.0f);
const vector3f dataSize(s_texture_size_small, s_texture_size_small, 0.0f);
const Graphics::TextureDescriptor texDesc(
Graphics::TEXTURE_RGBA_8888,
dataSize, texSize, Graphics::LINEAR_CLAMP,
false, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
m_surfaceTextureSmall.Reset(Pi::renderer->CreateTexture(texDesc));
const Terrain *pTerrain = GetTerrain();
const double fracStep = 1.0 / double(s_texture_size_small - 1);
Graphics::TextureCubeData tcd;
std::unique_ptr<Color[]> bufs[NUM_PATCHES];
for (int i = 0; i < NUM_PATCHES; i++) {
Color *colors = new Color[(s_texture_size_small * s_texture_size_small)];
for (Uint32 v = 0; v < s_texture_size_small; v++) {
for (Uint32 u = 0; u < s_texture_size_small; u++) {
// where in this row & colum are we now.
const double ustep = double(u) * fracStep;
const double vstep = double(v) * fracStep;
// get point on the surface of the sphere
const vector3d p = GetSpherePointFromCorners(ustep, vstep, &GetPatchFaces(i, 0));
// get colour using `p`
const vector3d colour = pTerrain->GetColor(p, 0.0, p);
// convert to ubyte and store
Color *col = colors + (u + (v * s_texture_size_small));
col[0].r = Uint8(colour.x * 255.0);
col[0].g = Uint8(colour.y * 255.0);
col[0].b = Uint8(colour.z * 255.0);
col[0].a = 255;
}
}
bufs[i].reset(colors);
}
// update with buffer from above
tcd.posX = bufs[0].get();
tcd.negX = bufs[1].get();
tcd.posY = bufs[2].get();
tcd.negY = bufs[3].get();
tcd.posZ = bufs[4].get();
tcd.negZ = bufs[5].get();
m_surfaceTextureSmall->Update(tcd, dataSize, Graphics::TEXTURE_RGBA_8888);
}
// create small texture
if (!bEnableGPUJobs) {
for (int i = 0; i < NUM_PATCHES; i++) {
assert(!m_hasJobRequest[i]);
assert(!m_job[i].HasJob());
m_hasJobRequest[i] = true;
GasGiantJobs::STextureFaceRequest *ssrd = new GasGiantJobs::STextureFaceRequest(&GetPatchFaces(i, 0), GetSystemBody()->GetPath(), i, s_texture_size_cpu[Pi::detail.planets], GetTerrain());
m_job[i] = Pi::GetAsyncJobQueue()->Queue(new GasGiantJobs::SingleTextureFaceJob(ssrd));
}
} else {
// use m_surfaceTexture texture?
// create texture
const vector2f texSize(1.0f, 1.0f);
const vector3f dataSize(s_texture_size_gpu[Pi::detail.planets], s_texture_size_gpu[Pi::detail.planets], 0.0f);
const Graphics::TextureDescriptor texDesc(
Graphics::TEXTURE_RGBA_8888,
dataSize, texSize, Graphics::LINEAR_CLAMP,
true, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
m_builtTexture.Reset(Pi::renderer->CreateTexture(texDesc));
const std::string ColorFracName = GetTerrain()->GetColorFractalName();
Output("Color Fractal name: %s\n", ColorFracName.c_str());
Uint32 GasGiantType = Graphics::OGL::GEN_JUPITER_TEXTURE;
if (ColorFracName == GGSaturn) {
GasGiantType = Graphics::OGL::GEN_SATURN_TEXTURE;
} else if (ColorFracName == GGSaturn2) {
GasGiantType = Graphics::OGL::GEN_SATURN2_TEXTURE;
} else if (ColorFracName == GGNeptune) {
GasGiantType = Graphics::OGL::GEN_NEPTUNE_TEXTURE;
} else if (ColorFracName == GGNeptune2) {
GasGiantType = Graphics::OGL::GEN_NEPTUNE2_TEXTURE;
} else if (ColorFracName == GGUranus) {
GasGiantType = Graphics::OGL::GEN_URANUS_TEXTURE;
}
const Uint32 octaves = (Pi::config->Int("AMD_MESA_HACKS") == 0) ? s_noiseOctaves[Pi::detail.planets] : std::min(5U, s_noiseOctaves[Pi::detail.planets]);
GasGiantType = (octaves << 16) | GasGiantType;
assert(!m_hasGpuJobRequest);
assert(!m_gpuJob.HasJob());
Random rng(GetSystemBody()->GetSeed() + 4609837);
const std::string parentname = GetSystemBody()->GetParent()->GetName();
const float hueShift = (parentname == "Sol") ? 0.0f : float(((rng.Double() * 2.0) - 1.0) * 0.9);
GasGiantJobs::GenFaceQuad *pQuad = new GasGiantJobs::GenFaceQuad(Pi::renderer, vector2f(s_texture_size_gpu[Pi::detail.planets], s_texture_size_gpu[Pi::detail.planets]), s_quadRenderState, GasGiantType);
GasGiantJobs::SGPUGenRequest *pGPUReq = new GasGiantJobs::SGPUGenRequest(GetSystemBody()->GetPath(), s_texture_size_gpu[Pi::detail.planets], GetTerrain(), GetSystemBody()->GetRadius(), hueShift, pQuad, m_builtTexture.Get());
m_gpuJob = Pi::GetSyncJobQueue()->Queue(new GasGiantJobs::SingleGPUGenJob(pGPUReq));
m_hasGpuJobRequest = true;
}
}
void GasGiant::Update()
{
PROFILE_SCOPED()
// assuming that we haven't already generated the texture from the render call.
if (m_timeDelay > 0.0f) {
m_timeDelay -= Pi::game->GetTimeStep();
if (m_timeDelay <= 0.0001f && !m_surfaceTexture.Valid()) {
// Use the fact that we have a patch as a latch to prevent repeat generation requests.
if (m_patches[0].get())
return;
BuildFirstPatches();
return;
}
}
}
void GasGiant::Render(Graphics::Renderer *renderer, const matrix4x4d &modelView, vector3d campos, const float radius, const std::vector<Camera::Shadow> &shadows)
{
PROFILE_SCOPED()
if (!m_surfaceTexture.Valid()) {
// Use the fact that we have a patch as a latch to prevent repeat generation requests.
if (!m_patches[0].get()) {
BuildFirstPatches();
}
}
// store this for later usage in the update method.
m_tempCampos = campos;
m_hasTempCampos = true;
matrix4x4d trans = modelView;
trans.Translate(-campos.x, -campos.y, -campos.z);
renderer->SetTransform(matrix4x4f(trans)); //need to set this for the following line to work
matrix4x4d modv = matrix4x4d(renderer->GetTransform());
matrix4x4d proj = matrix4x4d(renderer->GetProjection());
Graphics::Frustum frustum(modv, proj);
// no frustum test of entire gasSphere, since Space::Render does this
// for each body using its GetBoundingRadius() value
//First draw - create materials (they do not change afterwards)
if (!m_surfaceMaterial)
SetUpMaterials();
{
//Update material parameters
//XXX no need to calculate AP every frame
m_materialParameters.atmosphere = GetSystemBody()->CalcAtmosphereParams();
m_materialParameters.atmosphere.center = trans * vector3d(0.0, 0.0, 0.0);
m_materialParameters.atmosphere.planetRadius = radius;
m_materialParameters.shadows = shadows;
m_surfaceMaterial->specialParameter0 = &m_materialParameters;
if (m_materialParameters.atmosphere.atmosDensity > 0.0) {
m_atmosphereMaterial->specialParameter0 = &m_materialParameters;
// make atmosphere sphere slightly bigger than required so
// that the edges of the pixel shader atmosphere jizz doesn't
// show ugly polygonal angles
DrawAtmosphereSurface(renderer, trans, campos, m_materialParameters.atmosphere.atmosRadius * 1.01, m_atmosRenderState, m_atmosphereMaterial);
}
}
Color ambient;
// save old global ambient
const Color oldAmbient = renderer->GetAmbientColor();
{
// give planet some ambient lighting if the viewer is close to it
double camdist = campos.Length();
camdist = 0.1 / (camdist * camdist);
// why the fuck is this returning 0.1 when we are sat on the planet??
// JJ: Because campos is relative to a unit-radius planet - 1.0 at the surface
// XXX oh well, it is the value we want anyway...
ambient.r = ambient.g = ambient.b = camdist * 255;
ambient.a = 255;
}
renderer->SetAmbientColor(ambient);
renderer->SetTransform(matrix4x4f(modelView));
for (int i = 0; i < NUM_PATCHES; i++) {
m_patches[i]->Render(renderer, campos, modelView, frustum);
}
m_surfaceMaterial->Unapply();
renderer->SetAmbientColor(oldAmbient);
renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_GASGIANTS, 1);
}
void GasGiant::SetUpMaterials()
{
//solid
Graphics::RenderStateDesc rsd;
m_surfRenderState = Pi::renderer->CreateRenderState(rsd);
//blended
rsd.blendMode = Graphics::BLEND_ALPHA_ONE;
rsd.cullMode = Graphics::CULL_NONE;
rsd.depthWrite = false;
m_atmosRenderState = Pi::renderer->CreateRenderState(rsd);
// Request material for this planet, with atmosphere.
// Separate materials for surface and sky.
Graphics::MaterialDescriptor surfDesc;
surfDesc.effect = Graphics::EFFECT_GASSPHERE_TERRAIN;
//planetoid with atmosphere
const AtmosphereParameters ap(GetSystemBody()->CalcAtmosphereParams());
surfDesc.lighting = true;
assert(ap.atmosDensity > 0.0);
{
surfDesc.quality |= Graphics::HAS_ATMOSPHERE;
}
surfDesc.quality |= Graphics::HAS_ECLIPSES;
surfDesc.textures = 1;
assert(m_surfaceTextureSmall.Valid() || m_surfaceTexture.Valid());
m_surfaceMaterial.Reset(Pi::renderer->CreateMaterial(surfDesc));
m_surfaceMaterial->texture0 = m_surfaceTexture.Valid() ? m_surfaceTexture.Get() : m_surfaceTextureSmall.Get();
{
Graphics::MaterialDescriptor skyDesc;
skyDesc.effect = Graphics::EFFECT_GEOSPHERE_SKY;
skyDesc.lighting = true;
skyDesc.quality |= Graphics::HAS_ECLIPSES;
m_atmosphereMaterial.Reset(Pi::renderer->CreateMaterial(skyDesc));
}
}
void GasGiant::BuildFirstPatches()
{
PROFILE_SCOPED()
if (s_patchContext.Get() == nullptr) {
s_patchContext.Reset(new GasPatchContext(127));
}
using namespace GasGiantJobs;
m_patches[0].reset(new GasPatch(s_patchContext, this, p1, p2, p3, p4));
m_patches[1].reset(new GasPatch(s_patchContext, this, p4, p3, p7, p8));
m_patches[2].reset(new GasPatch(s_patchContext, this, p1, p4, p8, p5));
m_patches[3].reset(new GasPatch(s_patchContext, this, p2, p1, p5, p6));
m_patches[4].reset(new GasPatch(s_patchContext, this, p3, p2, p6, p7));
m_patches[5].reset(new GasPatch(s_patchContext, this, p8, p7, p6, p5));
GenerateTexture();
}
void GasGiant::Init()
{
IniConfig cfg;
cfg.Read(FileSystem::gameDataFiles, "configs/GasGiants.ini");
// NB: limit the ranges of all values loaded from the file
// NB: round to the nearest power of 2 for all texture sizes
s_texture_size_small = ceil_pow2(Clamp(cfg.Int("texture_size_small", 16), 16, 64));
SplitData(cfg.String("texture_size_0"), s_texture_size_cpu[0], s_texture_size_gpu[0], s_noiseOctaves[0]);
SplitData(cfg.String("texture_size_1"), s_texture_size_cpu[1], s_texture_size_gpu[1], s_noiseOctaves[1]);
SplitData(cfg.String("texture_size_2"), s_texture_size_cpu[2], s_texture_size_gpu[2], s_noiseOctaves[2]);
SplitData(cfg.String("texture_size_3"), s_texture_size_cpu[3], s_texture_size_gpu[3], s_noiseOctaves[3]);
SplitData(cfg.String("texture_size_4"), s_texture_size_cpu[4], s_texture_size_gpu[4], s_noiseOctaves[4]);
s_initialCPUDelayTime = Clamp(cfg.Float("cpu_delay_time", 60.0f), 0.0f, 120.0f);
s_initialGPUDelayTime = Clamp(cfg.Float("gpu_delay_time", 5.0f), 0.0f, 120.0f);
if (s_patchContext.Get() == nullptr) {
s_patchContext.Reset(new GasPatchContext(127));
}
CreateRenderTarget(s_texture_size_gpu[Pi::detail.planets], s_texture_size_gpu[Pi::detail.planets]);
}
void GasGiant::Uninit()
{
s_patchContext.Reset();
}
//static
void GasGiant::CreateRenderTarget(const Uint16 width, const Uint16 height)
{
/* @fluffyfreak here's a rendertarget implementation you can use for oculusing and other things. It's pretty simple:
- fill out a RenderTargetDesc struct and call Renderer::CreateRenderTarget
- pass target to Renderer::SetRenderTarget to start rendering to texture
- set up viewport, clear etc, then draw as usual
- SetRenderTarget(0) to resume render to screen
- you can access the attached texture with GetColorTexture to use it with a material
You can reuse the same target with multiple textures.
In that case, leave the color format to NONE so the initial texture is not created, then use SetColorTexture to attach your own.
*/
Graphics::RenderStateDesc rsd;
rsd.depthTest = false;
rsd.depthWrite = false;
rsd.blendMode = Graphics::BLEND_ALPHA;
s_quadRenderState = Pi::renderer->CreateRenderState(rsd);
// Complete the RT description so we can request a buffer.
// NB: we don't want it to create use a texture because we share it with the textured quad created above.
Graphics::RenderTargetDesc rtDesc(
width,
height,
Graphics::TEXTURE_NONE, // don't create a texture
Graphics::TEXTURE_NONE, // don't create a depth buffer
false);
s_renderTarget = Pi::renderer->CreateRenderTarget(rtDesc);
}
//static
void GasGiant::SetRenderTargetCubemap(const Uint32 face, Graphics::Texture *pTexture, const bool unBind /*= true*/)
{
s_renderTarget->SetCubeFaceTexture(face, pTexture);
}
//static
void GasGiant::BeginRenderTarget()
{
Pi::renderer->SetRenderTarget(s_renderTarget);
}
//static
void GasGiant::EndRenderTarget()
{
Pi::renderer->SetRenderTarget(nullptr);
}