872 lines
27 KiB
C++
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);
|
|
}
|