1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "GasGiant.h"
5 
6 #include "FileSystem.h"
7 #include "Game.h"
8 #include "GameConfig.h"
9 #include "Pi.h"
10 #include "galaxy/AtmosphereParameters.h"
11 #include "graphics/Frustum.h"
12 #include "graphics/Graphics.h"
13 #include "graphics/Material.h"
14 #include "graphics/RenderState.h"
15 #include "graphics/Renderer.h"
16 #include "graphics/Texture.h"
17 #include "graphics/VertexArray.h"
18 #include "graphics/opengl/GenGasGiantColourMaterial.h"
19 #include "perlin.h"
20 #include "utils.h"
21 #include "vcacheopt/vcacheopt.h"
22 
23 RefCountedPtr<GasPatchContext> GasGiant::s_patchContext;
24 
25 namespace {
26 	static Uint32 s_texture_size_small = 16;
27 	static Uint32 s_texture_size_cpu[5];
28 	static Uint32 s_texture_size_gpu[5];
29 	static Uint32 s_noiseOctaves[5];
30 	static float s_initialCPUDelayTime = 60.0f; // (perhaps) 60 seconds seems like a reasonable default
31 	static float s_initialGPUDelayTime = 5.0f;	// (perhaps) 5 seconds seems like a reasonable default
32 	static std::vector<GasGiant *> s_allGasGiants;
33 
34 	static const std::string GGJupiter("GGJupiter");
35 	static const std::string GGNeptune("GGNeptune");
36 	static const std::string GGNeptune2("GGNeptune2");
37 	static const std::string GGSaturn("GGSaturn");
38 	static const std::string GGSaturn2("GGSaturn2");
39 	static const std::string GGUranus("GGUranus");
40 
SplitData(const std::string & spec,Uint32 & cpuOut,Uint32 & gpuOut,Uint32 & octavesOut)41 	bool SplitData(const std::string &spec, Uint32 &cpuOut, Uint32 &gpuOut, Uint32 &octavesOut)
42 	{
43 		static const std::string delim(",");
44 
45 		enum dataEntries {
46 			eCPU = 0,
47 			eGPU,
48 			eOCTAVES
49 		};
50 
51 		size_t i = 0, start = 0, end = 0;
52 		while (end != std::string::npos) {
53 			// get to the first non-delim char
54 			start = spec.find_first_not_of(delim, end);
55 
56 			// read the end, no more to do
57 			if (start == std::string::npos)
58 				break;
59 
60 			// find the end - next delim or end of string
61 			end = spec.find_first_of(delim, start);
62 
63 			// extract the fragment and remember it
64 			switch (i) {
65 			case eCPU:
66 				cpuOut = ceil_pow2(Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 64, 4096));
67 				break;
68 			case eGPU:
69 				gpuOut = ceil_pow2(Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 64, 4096));
70 				break;
71 			case eOCTAVES:
72 				octavesOut = Clamp(atoi(spec.substr(start, (end == std::string::npos) ? std::string::npos : end - start).c_str()), 1, 16);
73 				break;
74 			default:
75 				assert(false);
76 				break;
77 			}
78 			i++;
79 		}
80 
81 		return i == 4;
82 	}
83 } // namespace
84 
85 class GasPatchContext : public RefCounted {
86 public:
87 #pragma pack(push, 4)
88 	struct VBOVertex {
89 		vector3f pos;
90 		vector3f norm;
91 	};
92 #pragma pack(pop)
93 
94 	int edgeLen;
95 
IDX_VBO_COUNT_ALL_IDX() const96 	inline int IDX_VBO_COUNT_ALL_IDX() const { return ((edgeLen - 1) * (edgeLen - 1)) * 2 * 3; }
97 
NUMVERTICES() const98 	inline int NUMVERTICES() const { return edgeLen * edgeLen; }
99 
100 	double frac;
101 
102 	std::unique_ptr<Uint32[]> indices;
103 	RefCountedPtr<Graphics::IndexBuffer> indexBuffer;
104 
GasPatchContext(const int _edgeLen)105 	GasPatchContext(const int _edgeLen) :
106 		edgeLen(_edgeLen)
107 	{
108 		Init();
109 	}
110 
~GasPatchContext()111 	~GasPatchContext()
112 	{
113 		Cleanup();
114 	}
115 
Refresh()116 	void Refresh()
117 	{
118 		Cleanup();
119 		Init();
120 	}
121 
Cleanup()122 	void Cleanup()
123 	{
124 		indices.reset();
125 	}
126 
GetIndices(std::vector<Uint32> & pl)127 	int GetIndices(std::vector<Uint32> &pl)
128 	{
129 		// calculate how many tri's there are
130 		const int tri_count = IDX_VBO_COUNT_ALL_IDX() / 3;
131 
132 		// pre-allocate enough space
133 		pl.reserve(IDX_VBO_COUNT_ALL_IDX());
134 
135 		// add all of the middle indices
136 		for (int i = 0; i < IDX_VBO_COUNT_ALL_IDX(); ++i) {
137 			pl.push_back(indices[i]);
138 		}
139 
140 		return tri_count;
141 	}
142 
Init()143 	void Init()
144 	{
145 		PROFILE_SCOPED()
146 		frac = 1.0 / double(edgeLen - 1);
147 
148 		// also want vtx indices for tris not touching edge of patch
149 		indices.reset(new Uint32[IDX_VBO_COUNT_ALL_IDX()]);
150 		Uint32 *idx = indices.get();
151 		for (int x = 0; x < edgeLen - 1; x++) {
152 			for (int y = 0; y < edgeLen - 1; y++) {
153 				idx[0] = x + edgeLen * y;
154 				idx[1] = x + 1 + edgeLen * y;
155 				idx[2] = x + edgeLen * (y + 1);
156 				idx += 3;
157 
158 				idx[0] = x + 1 + edgeLen * y;
159 				idx[1] = x + 1 + edgeLen * (y + 1);
160 				idx[2] = x + edgeLen * (y + 1);
161 				idx += 3;
162 			}
163 		}
164 
165 		// these will hold the optimised indices
166 		std::vector<Uint32> pl_short;
167 
168 		// populate the N indices lists from the arrays built during InitTerrainIndices()
169 		// iterate over each index list and optimize it
170 		Uint32 tri_count = GetIndices(pl_short);
171 		VertexCacheOptimizerUInt vco;
172 #ifndef NDEBUG
173 		VertexCacheOptimizerUInt::Result res = vco.Optimize(&pl_short[0], tri_count);
174 		assert(0 == res);
175 #else
176 		vco.Optimize(&pl_short[0], tri_count);
177 #endif
178 
179 		//create buffer & copy
180 		indexBuffer.Reset(Pi::renderer->CreateIndexBuffer(pl_short.size(), Graphics::BUFFER_USAGE_STATIC));
181 		Uint32 *idxPtr = indexBuffer->Map(Graphics::BUFFER_MAP_WRITE);
182 		for (Uint32 j = 0; j < pl_short.size(); j++) {
183 			idxPtr[j] = pl_short[j];
184 		}
185 		indexBuffer->Unmap();
186 
187 		if (indices) {
188 			indices.reset();
189 		}
190 	}
191 };
192 
193 class GasPatch {
194 public:
195 	RefCountedPtr<GasPatchContext> ctx;
196 	vector3d v[4];
197 	std::unique_ptr<Graphics::VertexBuffer> m_vertexBuffer;
198 	GasGiant *gasSphere;
199 	vector3d clipCentroid;
200 	double clipRadius;
201 
GasPatch(const RefCountedPtr<GasPatchContext> & _ctx,GasGiant * gs,vector3d v0,vector3d v1,vector3d v2,vector3d v3)202 	GasPatch(const RefCountedPtr<GasPatchContext> &_ctx, GasGiant *gs, vector3d v0, vector3d v1, vector3d v2, vector3d v3) :
203 		ctx(_ctx),
204 		gasSphere(gs),
205 		clipCentroid(((v0 + v1 + v2 + v3) * 0.25).Normalized()),
206 		clipRadius(0.0)
207 	{
208 		PROFILE_SCOPED()
209 		v[0] = v0;
210 		v[1] = v1;
211 		v[2] = v2;
212 		v[3] = v3;
213 		for (int i = 0; i < 4; i++) {
214 			clipRadius = std::max(clipRadius, (v[i] - clipCentroid).Length());
215 		}
216 
217 		UpdateVBOs();
218 	}
219 
~GasPatch()220 	~GasPatch() {}
221 
222 	/* in patch surface coords, [0,1] */
GetSpherePoint(const double x,const double y) const223 	vector3d GetSpherePoint(const double x, const double y) const
224 	{
225 		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();
226 	}
227 
UpdateVBOs()228 	void UpdateVBOs()
229 	{
230 		PROFILE_SCOPED()
231 		//create buffer and upload data
232 		Graphics::VertexBufferDesc vbd;
233 		vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
234 		vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
235 		vbd.attrib[1].semantic = Graphics::ATTRIB_NORMAL;
236 		vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_FLOAT3;
237 		vbd.numVertices = ctx->NUMVERTICES();
238 		vbd.usage = Graphics::BUFFER_USAGE_STATIC;
239 		m_vertexBuffer.reset(Pi::renderer->CreateVertexBuffer(vbd));
240 
241 		GasPatchContext::VBOVertex *vtxPtr = m_vertexBuffer->Map<GasPatchContext::VBOVertex>(Graphics::BUFFER_MAP_WRITE);
242 		assert(m_vertexBuffer->GetDesc().stride == sizeof(GasPatchContext::VBOVertex));
243 
244 		const Sint32 edgeLen = ctx->edgeLen;
245 		const double frac = ctx->frac;
246 		for (Sint32 y = 0; y < edgeLen; y++) {
247 			for (Sint32 x = 0; x < edgeLen; x++) {
248 				const vector3d p = GetSpherePoint(x * frac, y * frac);
249 				const vector3d pSubCentroid = p - clipCentroid;
250 				clipRadius = std::max(clipRadius, p.Length());
251 				vtxPtr->pos = vector3f(pSubCentroid);
252 				vtxPtr->norm = vector3f(p);
253 
254 				++vtxPtr; // next vertex
255 			}
256 		}
257 		m_vertexBuffer->Unmap();
258 	}
259 
Render(Graphics::Renderer * renderer,const vector3d & campos,const matrix4x4d & modelView,const Graphics::Frustum & frustum)260 	void Render(Graphics::Renderer *renderer, const vector3d &campos, const matrix4x4d &modelView, const Graphics::Frustum &frustum)
261 	{
262 		if (!frustum.TestPoint(clipCentroid, clipRadius))
263 			return;
264 
265 		RefCountedPtr<Graphics::Material> mat = gasSphere->GetSurfaceMaterial();
266 		Graphics::RenderState *rs = gasSphere->GetSurfRenderState();
267 
268 		const vector3d relpos = clipCentroid - campos;
269 		renderer->SetTransform(matrix4x4f(modelView * matrix4x4d::Translation(relpos)));
270 
271 		Pi::statSceneTris += 2 * (ctx->edgeLen - 1) * (ctx->edgeLen - 1);
272 		++Pi::statNumPatches;
273 
274 		renderer->DrawBufferIndexed(m_vertexBuffer.get(), ctx->indexBuffer.Get(), rs, mat.Get());
275 		renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_PATCHES, 1);
276 	}
277 };
278 
279 Graphics::RenderTarget *GasGiant::s_renderTarget;
280 Graphics::RenderState *GasGiant::s_quadRenderState;
281 
282 // static
UpdateAllGasGiants()283 void GasGiant::UpdateAllGasGiants()
284 {
285 	PROFILE_SCOPED()
286 	for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(); i != s_allGasGiants.end(); ++i) {
287 		(*i)->Update();
288 	}
289 }
290 
291 // static
OnChangeDetailLevel()292 void GasGiant::OnChangeDetailLevel()
293 {
294 	s_patchContext.Reset(new GasPatchContext(127));
295 
296 	// reinit the geosphere terrain data
297 	for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(); i != s_allGasGiants.end(); ++i) {
298 		// clearout anything we don't need
299 		(*i)->Reset();
300 
301 		// reinit the terrain with the new settings
302 		(*i)->m_terrain.Reset(Terrain::InstanceTerrain((*i)->GetSystemBody()));
303 	}
304 }
305 
GasGiant(const SystemBody * body)306 GasGiant::GasGiant(const SystemBody *body) :
307 	BaseSphere(body),
308 	m_hasTempCampos(false),
309 	m_tempCampos(0.0),
310 	m_hasGpuJobRequest(false),
311 	m_timeDelay(s_initialCPUDelayTime)
312 {
313 	s_allGasGiants.push_back(this);
314 
315 	for (int i = 0; i < NUM_PATCHES; i++) {
316 		m_hasJobRequest[i] = false;
317 	}
318 
319 	Random rng(GetSystemBody()->GetSeed() + 4609837);
320 
321 	const bool bEnableGPUJobs = (Pi::config->Int("EnableGPUJobs") == 1);
322 	if (bEnableGPUJobs)
323 		m_timeDelay = s_initialGPUDelayTime + (rng.Double() * (s_initialGPUDelayTime * 0.5));
324 
325 	//SetUpMaterials is not called until first Render since light count is zero :)
326 
327 	//BuildFirstPatches and GenerateTexture are only called when we first attempt to render
328 }
329 
~GasGiant()330 GasGiant::~GasGiant()
331 {
332 	// update thread should not be able to access us now, so we can safely continue to delete
333 	assert(std::count(s_allGasGiants.begin(), s_allGasGiants.end(), this) == 1);
334 	s_allGasGiants.erase(std::find(s_allGasGiants.begin(), s_allGasGiants.end(), this));
335 }
336 
Reset()337 void GasGiant::Reset()
338 {
339 	{
340 		for (int i = 0; i < NUM_PATCHES; i++) {
341 			if (m_hasJobRequest[i] && m_job[i].HasJob())
342 				m_job[i].GetJob()->OnCancel();
343 			m_hasJobRequest[i] = false;
344 		}
345 	}
346 
347 	for (int p = 0; p < NUM_PATCHES; p++) {
348 		// delete patches
349 		if (m_patches[p]) {
350 			m_patches[p].reset();
351 		}
352 	}
353 
354 	m_surfaceTextureSmall.Reset();
355 	m_surfaceTexture.Reset();
356 	m_surfaceMaterial.Reset();
357 }
358 
359 //static
OnAddTextureFaceResult(const SystemPath & path,GasGiantJobs::STextureFaceResult * res)360 bool GasGiant::OnAddTextureFaceResult(const SystemPath &path, GasGiantJobs::STextureFaceResult *res)
361 {
362 	// Find the correct GeoSphere via it's system path, and give it the split result
363 	for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(), iEnd = s_allGasGiants.end(); i != iEnd; ++i) {
364 		if (path == (*i)->GetSystemBody()->GetPath()) {
365 			(*i)->AddTextureFaceResult(res);
366 			return true;
367 		}
368 	}
369 	// GasGiant not found to return the data to, cancel (which deletes it) instead
370 	if (res) {
371 		res->OnCancel();
372 		delete res;
373 	}
374 	return false;
375 }
376 
377 //static
OnAddGPUGenResult(const SystemPath & path,GasGiantJobs::SGPUGenResult * res)378 bool GasGiant::OnAddGPUGenResult(const SystemPath &path, GasGiantJobs::SGPUGenResult *res)
379 {
380 	// Find the correct GeoSphere via it's system path, and give it the split result
381 	for (std::vector<GasGiant *>::iterator i = s_allGasGiants.begin(), iEnd = s_allGasGiants.end(); i != iEnd; ++i) {
382 		if (path == (*i)->GetSystemBody()->GetPath()) {
383 			(*i)->AddGPUGenResult(res);
384 			return true;
385 		}
386 	}
387 	// GasGiant not found to return the data to, cancel (which deletes it) instead
388 	if (res) {
389 		res->OnCancel();
390 		delete res;
391 	}
392 	return false;
393 }
394 
395 #define DUMP_TO_TEXTURE 0
396 
397 #if DUMP_TO_TEXTURE
398 #include "FileSystem.h"
399 #include "PngWriter.h"
400 #include "graphics/opengl/TextureGL.h"
textureDump(const char * destFile,const int width,const int height,const Color * buf)401 void textureDump(const char *destFile, const int width, const int height, const Color *buf)
402 {
403 	const std::string dir = "generated_textures";
404 	FileSystem::userFiles.MakeDirectory(dir);
405 	const std::string fname = FileSystem::JoinPathBelow(dir, destFile);
406 
407 	// pad rows to 4 bytes, which is the default row alignment for OpenGL
408 	//const int stride = (3*width + 3) & ~3;
409 	const int stride = width * 4;
410 
411 	write_png(FileSystem::userFiles, fname, &buf[0].r, width, height, stride, 4);
412 
413 	printf("texture %s saved\n", fname.c_str());
414 }
415 #endif
416 
AddTextureFaceResult(GasGiantJobs::STextureFaceResult * res)417 bool GasGiant::AddTextureFaceResult(GasGiantJobs::STextureFaceResult *res)
418 {
419 	bool result = false;
420 	assert(res);
421 	assert(res->face() >= 0 && res->face() < NUM_PATCHES);
422 	m_jobColorBuffers[res->face()].reset(res->data().colors);
423 	m_hasJobRequest[res->face()] = false;
424 	const Sint32 uvDims = res->data().uvDims;
425 	assert(uvDims > 0 && uvDims <= 4096);
426 
427 	// tidyup
428 	delete res;
429 
430 	bool bCreateTexture = true;
431 	for (int i = 0; i < NUM_PATCHES; i++) {
432 		bCreateTexture = bCreateTexture & (!m_hasJobRequest[i]);
433 	}
434 
435 	if (bCreateTexture) {
436 		// create texture
437 		const vector2f texSize(1.0f, 1.0f);
438 		const vector3f dataSize(uvDims, uvDims, 0.0f);
439 		const Graphics::TextureDescriptor texDesc(
440 			Graphics::TEXTURE_RGBA_8888,
441 			dataSize, texSize, Graphics::LINEAR_CLAMP,
442 			true, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
443 		m_surfaceTexture.Reset(Pi::renderer->CreateTexture(texDesc));
444 
445 		// update with buffer from above
446 		Graphics::TextureCubeData tcd;
447 		tcd.posX = m_jobColorBuffers[0].get();
448 		tcd.negX = m_jobColorBuffers[1].get();
449 		tcd.posY = m_jobColorBuffers[2].get();
450 		tcd.negY = m_jobColorBuffers[3].get();
451 		tcd.posZ = m_jobColorBuffers[4].get();
452 		tcd.negZ = m_jobColorBuffers[5].get();
453 		m_surfaceTexture->Update(tcd, dataSize, Graphics::TEXTURE_RGBA_8888);
454 
455 #if DUMP_TO_TEXTURE
456 		for (int iFace = 0; iFace < NUM_PATCHES; iFace++) {
457 			char filename[1024];
458 			snprintf(filename, 1024, "%s%d.png", GetSystemBody()->GetName().c_str(), iFace);
459 			textureDump(filename, uvDims, uvDims, m_jobColorBuffers[iFace].get());
460 		}
461 #endif
462 
463 		// cleanup the temporary color buffer storage
464 		for (int i = 0; i < NUM_PATCHES; i++) {
465 			m_jobColorBuffers[i].reset();
466 		}
467 
468 		// change the planet texture for the new higher resolution texture
469 		if (m_surfaceMaterial.Get()) {
470 			m_surfaceMaterial->texture0 = m_surfaceTexture.Get();
471 			m_surfaceTextureSmall.Reset();
472 		}
473 	}
474 
475 	return result;
476 }
477 
AddGPUGenResult(GasGiantJobs::SGPUGenResult * res)478 bool GasGiant::AddGPUGenResult(GasGiantJobs::SGPUGenResult *res)
479 {
480 	bool result = false;
481 	assert(res);
482 	m_hasGpuJobRequest = false;
483 	assert(!m_gpuJob.HasJob());
484 #ifndef NDEBUG
485 	const Sint32 uvDims = res->data().uvDims;
486 	assert(uvDims > 0 && uvDims <= 4096);
487 #endif
488 
489 #if DUMP_TO_TEXTURE
490 	for (int iFace = 0; iFace < NUM_PATCHES; iFace++) {
491 		std::unique_ptr<Color, FreeDeleter> buffer(static_cast<Color *>(malloc(uvDims * uvDims * 4)));
492 		Graphics::Texture *pTex = res->data().texture.Get();
493 		Graphics::TextureGL *pGLTex = static_cast<Graphics::TextureGL *>(pTex);
494 		pGLTex->Bind();
495 		glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + iFace, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.get());
496 		pGLTex->Unbind();
497 
498 		char filename[1024];
499 		snprintf(filename, 1024, "%s%d.png", GetSystemBody()->GetName().c_str(), iFace);
500 		textureDump(filename, uvDims, uvDims, buffer.get());
501 	}
502 #endif
503 
504 	// tidyup
505 	delete res;
506 
507 	if (m_builtTexture.Valid()) {
508 		m_surfaceTexture = m_builtTexture;
509 		m_builtTexture.Reset();
510 
511 		// these won't be automatically generated otherwise since we used it as a render target
512 		m_surfaceTexture->BuildMipmaps();
513 
514 		// change the planet texture for the new higher resolution texture
515 		if (m_surfaceMaterial.Get()) {
516 			m_surfaceMaterial->texture0 = m_surfaceTexture.Get();
517 			m_surfaceTextureSmall.Reset();
518 		}
519 	}
520 
521 	return result;
522 }
523 
524 // in patch surface coords, [0,1]
GetSpherePointFromCorners(const double x,const double y,const vector3d * corners)525 inline vector3d GetSpherePointFromCorners(const double x, const double y, const vector3d *corners)
526 {
527 	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();
528 }
529 
GenerateTexture()530 void GasGiant::GenerateTexture()
531 {
532 	using namespace GasGiantJobs;
533 	for (int i = 0; i < NUM_PATCHES; i++) {
534 		if (m_hasGpuJobRequest || m_hasJobRequest[i])
535 			return;
536 	}
537 
538 	const bool bEnableGPUJobs = (Pi::config->Int("EnableGPUJobs") == 1);
539 
540 	// scope the small texture generation
541 	{
542 		const vector2f texSize(1.0f, 1.0f);
543 		const vector3f dataSize(s_texture_size_small, s_texture_size_small, 0.0f);
544 		const Graphics::TextureDescriptor texDesc(
545 			Graphics::TEXTURE_RGBA_8888,
546 			dataSize, texSize, Graphics::LINEAR_CLAMP,
547 			false, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
548 		m_surfaceTextureSmall.Reset(Pi::renderer->CreateTexture(texDesc));
549 
550 		const Terrain *pTerrain = GetTerrain();
551 		const double fracStep = 1.0 / double(s_texture_size_small - 1);
552 
553 		Graphics::TextureCubeData tcd;
554 		std::unique_ptr<Color[]> bufs[NUM_PATCHES];
555 		for (int i = 0; i < NUM_PATCHES; i++) {
556 			Color *colors = new Color[(s_texture_size_small * s_texture_size_small)];
557 			for (Uint32 v = 0; v < s_texture_size_small; v++) {
558 				for (Uint32 u = 0; u < s_texture_size_small; u++) {
559 					// where in this row & colum are we now.
560 					const double ustep = double(u) * fracStep;
561 					const double vstep = double(v) * fracStep;
562 
563 					// get point on the surface of the sphere
564 					const vector3d p = GetSpherePointFromCorners(ustep, vstep, &GetPatchFaces(i, 0));
565 					// get colour using `p`
566 					const vector3d colour = pTerrain->GetColor(p, 0.0, p);
567 
568 					// convert to ubyte and store
569 					Color *col = colors + (u + (v * s_texture_size_small));
570 					col[0].r = Uint8(colour.x * 255.0);
571 					col[0].g = Uint8(colour.y * 255.0);
572 					col[0].b = Uint8(colour.z * 255.0);
573 					col[0].a = 255;
574 				}
575 			}
576 			bufs[i].reset(colors);
577 		}
578 
579 		// update with buffer from above
580 		tcd.posX = bufs[0].get();
581 		tcd.negX = bufs[1].get();
582 		tcd.posY = bufs[2].get();
583 		tcd.negY = bufs[3].get();
584 		tcd.posZ = bufs[4].get();
585 		tcd.negZ = bufs[5].get();
586 		m_surfaceTextureSmall->Update(tcd, dataSize, Graphics::TEXTURE_RGBA_8888);
587 	}
588 
589 	// create small texture
590 	if (!bEnableGPUJobs) {
591 		for (int i = 0; i < NUM_PATCHES; i++) {
592 			assert(!m_hasJobRequest[i]);
593 			assert(!m_job[i].HasJob());
594 			m_hasJobRequest[i] = true;
595 			GasGiantJobs::STextureFaceRequest *ssrd = new GasGiantJobs::STextureFaceRequest(&GetPatchFaces(i, 0), GetSystemBody()->GetPath(), i, s_texture_size_cpu[Pi::detail.planets], GetTerrain());
596 			m_job[i] = Pi::GetAsyncJobQueue()->Queue(new GasGiantJobs::SingleTextureFaceJob(ssrd));
597 		}
598 	} else {
599 		// use m_surfaceTexture texture?
600 		// create texture
601 		const vector2f texSize(1.0f, 1.0f);
602 		const vector3f dataSize(s_texture_size_gpu[Pi::detail.planets], s_texture_size_gpu[Pi::detail.planets], 0.0f);
603 		const Graphics::TextureDescriptor texDesc(
604 			Graphics::TEXTURE_RGBA_8888,
605 			dataSize, texSize, Graphics::LINEAR_CLAMP,
606 			true, false, false, 0, Graphics::TEXTURE_CUBE_MAP);
607 		m_builtTexture.Reset(Pi::renderer->CreateTexture(texDesc));
608 
609 		const std::string ColorFracName = GetTerrain()->GetColorFractalName();
610 		Output("Color Fractal name: %s\n", ColorFracName.c_str());
611 
612 		Uint32 GasGiantType = Graphics::OGL::GEN_JUPITER_TEXTURE;
613 		if (ColorFracName == GGSaturn) {
614 			GasGiantType = Graphics::OGL::GEN_SATURN_TEXTURE;
615 		} else if (ColorFracName == GGSaturn2) {
616 			GasGiantType = Graphics::OGL::GEN_SATURN2_TEXTURE;
617 		} else if (ColorFracName == GGNeptune) {
618 			GasGiantType = Graphics::OGL::GEN_NEPTUNE_TEXTURE;
619 		} else if (ColorFracName == GGNeptune2) {
620 			GasGiantType = Graphics::OGL::GEN_NEPTUNE2_TEXTURE;
621 		} else if (ColorFracName == GGUranus) {
622 			GasGiantType = Graphics::OGL::GEN_URANUS_TEXTURE;
623 		}
624 		const Uint32 octaves = (Pi::config->Int("AMD_MESA_HACKS") == 0) ? s_noiseOctaves[Pi::detail.planets] : std::min(5U, s_noiseOctaves[Pi::detail.planets]);
625 		GasGiantType = (octaves << 16) | GasGiantType;
626 
627 		assert(!m_hasGpuJobRequest);
628 		assert(!m_gpuJob.HasJob());
629 
630 		Random rng(GetSystemBody()->GetSeed() + 4609837);
631 		const std::string parentname = GetSystemBody()->GetParent()->GetName();
632 		const float hueShift = (parentname == "Sol") ? 0.0f : float(((rng.Double() * 2.0) - 1.0) * 0.9);
633 
634 		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);
635 
636 		GasGiantJobs::SGPUGenRequest *pGPUReq = new GasGiantJobs::SGPUGenRequest(GetSystemBody()->GetPath(), s_texture_size_gpu[Pi::detail.planets], GetTerrain(), GetSystemBody()->GetRadius(), hueShift, pQuad, m_builtTexture.Get());
637 		m_gpuJob = Pi::GetSyncJobQueue()->Queue(new GasGiantJobs::SingleGPUGenJob(pGPUReq));
638 		m_hasGpuJobRequest = true;
639 	}
640 }
641 
Update()642 void GasGiant::Update()
643 {
644 	PROFILE_SCOPED()
645 	// assuming that we haven't already generated the texture from the render call.
646 	if (m_timeDelay > 0.0f) {
647 		m_timeDelay -= Pi::game->GetTimeStep();
648 		if (m_timeDelay <= 0.0001f && !m_surfaceTexture.Valid()) {
649 			// Use the fact that we have a patch as a latch to prevent repeat generation requests.
650 			if (m_patches[0].get())
651 				return;
652 
653 			BuildFirstPatches();
654 			return;
655 		}
656 	}
657 }
658 
Render(Graphics::Renderer * renderer,const matrix4x4d & modelView,vector3d campos,const float radius,const std::vector<Camera::Shadow> & shadows)659 void GasGiant::Render(Graphics::Renderer *renderer, const matrix4x4d &modelView, vector3d campos, const float radius, const std::vector<Camera::Shadow> &shadows)
660 {
661 	PROFILE_SCOPED()
662 	if (!m_surfaceTexture.Valid()) {
663 		// Use the fact that we have a patch as a latch to prevent repeat generation requests.
664 		if (!m_patches[0].get()) {
665 			BuildFirstPatches();
666 		}
667 	}
668 
669 	// store this for later usage in the update method.
670 	m_tempCampos = campos;
671 	m_hasTempCampos = true;
672 
673 	matrix4x4d trans = modelView;
674 	trans.Translate(-campos.x, -campos.y, -campos.z);
675 	renderer->SetTransform(matrix4x4f(trans)); //need to set this for the following line to work
676 	matrix4x4d modv = matrix4x4d(renderer->GetTransform());
677 	matrix4x4d proj = matrix4x4d(renderer->GetProjection());
678 	Graphics::Frustum frustum(modv, proj);
679 
680 	// no frustum test of entire gasSphere, since Space::Render does this
681 	// for each body using its GetBoundingRadius() value
682 
683 	//First draw - create materials (they do not change afterwards)
684 	if (!m_surfaceMaterial)
685 		SetUpMaterials();
686 
687 	{
688 		//Update material parameters
689 		//XXX no need to calculate AP every frame
690 		m_materialParameters.atmosphere = GetSystemBody()->CalcAtmosphereParams();
691 		m_materialParameters.atmosphere.center = trans * vector3d(0.0, 0.0, 0.0);
692 		m_materialParameters.atmosphere.planetRadius = radius;
693 
694 		m_materialParameters.shadows = shadows;
695 
696 		m_surfaceMaterial->specialParameter0 = &m_materialParameters;
697 
698 		if (m_materialParameters.atmosphere.atmosDensity > 0.0) {
699 			m_atmosphereMaterial->specialParameter0 = &m_materialParameters;
700 
701 			// make atmosphere sphere slightly bigger than required so
702 			// that the edges of the pixel shader atmosphere jizz doesn't
703 			// show ugly polygonal angles
704 			DrawAtmosphereSurface(renderer, trans, campos, m_materialParameters.atmosphere.atmosRadius * 1.01, m_atmosRenderState, m_atmosphereMaterial);
705 		}
706 	}
707 
708 	Color ambient;
709 
710 	// save old global ambient
711 	const Color oldAmbient = renderer->GetAmbientColor();
712 
713 	{
714 		// give planet some ambient lighting if the viewer is close to it
715 		double camdist = campos.Length();
716 		camdist = 0.1 / (camdist * camdist);
717 		// why the fuck is this returning 0.1 when we are sat on the planet??
718 		// JJ: Because campos is relative to a unit-radius planet - 1.0 at the surface
719 		// XXX oh well, it is the value we want anyway...
720 		ambient.r = ambient.g = ambient.b = camdist * 255;
721 		ambient.a = 255;
722 	}
723 
724 	renderer->SetAmbientColor(ambient);
725 
726 	renderer->SetTransform(matrix4x4f(modelView));
727 
728 	for (int i = 0; i < NUM_PATCHES; i++) {
729 		m_patches[i]->Render(renderer, campos, modelView, frustum);
730 	}
731 
732 	m_surfaceMaterial->Unapply();
733 
734 	renderer->SetAmbientColor(oldAmbient);
735 
736 	renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_GASGIANTS, 1);
737 }
738 
SetUpMaterials()739 void GasGiant::SetUpMaterials()
740 {
741 	//solid
742 	Graphics::RenderStateDesc rsd;
743 	m_surfRenderState = Pi::renderer->CreateRenderState(rsd);
744 
745 	//blended
746 	rsd.blendMode = Graphics::BLEND_ALPHA_ONE;
747 	rsd.cullMode = Graphics::CULL_NONE;
748 	rsd.depthWrite = false;
749 	m_atmosRenderState = Pi::renderer->CreateRenderState(rsd);
750 
751 	// Request material for this planet, with atmosphere.
752 	// Separate materials for surface and sky.
753 	Graphics::MaterialDescriptor surfDesc;
754 	surfDesc.effect = Graphics::EFFECT_GASSPHERE_TERRAIN;
755 
756 	//planetoid with atmosphere
757 	const AtmosphereParameters ap(GetSystemBody()->CalcAtmosphereParams());
758 	surfDesc.lighting = true;
759 	assert(ap.atmosDensity > 0.0);
760 	{
761 		surfDesc.quality |= Graphics::HAS_ATMOSPHERE;
762 	}
763 
764 	surfDesc.quality |= Graphics::HAS_ECLIPSES;
765 	surfDesc.textures = 1;
766 
767 	assert(m_surfaceTextureSmall.Valid() || m_surfaceTexture.Valid());
768 	m_surfaceMaterial.Reset(Pi::renderer->CreateMaterial(surfDesc));
769 	m_surfaceMaterial->texture0 = m_surfaceTexture.Valid() ? m_surfaceTexture.Get() : m_surfaceTextureSmall.Get();
770 
771 	{
772 		Graphics::MaterialDescriptor skyDesc;
773 		skyDesc.effect = Graphics::EFFECT_GEOSPHERE_SKY;
774 		skyDesc.lighting = true;
775 		skyDesc.quality |= Graphics::HAS_ECLIPSES;
776 		m_atmosphereMaterial.Reset(Pi::renderer->CreateMaterial(skyDesc));
777 	}
778 }
779 
BuildFirstPatches()780 void GasGiant::BuildFirstPatches()
781 {
782 	PROFILE_SCOPED()
783 	if (s_patchContext.Get() == nullptr) {
784 		s_patchContext.Reset(new GasPatchContext(127));
785 	}
786 
787 	using namespace GasGiantJobs;
788 	m_patches[0].reset(new GasPatch(s_patchContext, this, p1, p2, p3, p4));
789 	m_patches[1].reset(new GasPatch(s_patchContext, this, p4, p3, p7, p8));
790 	m_patches[2].reset(new GasPatch(s_patchContext, this, p1, p4, p8, p5));
791 	m_patches[3].reset(new GasPatch(s_patchContext, this, p2, p1, p5, p6));
792 	m_patches[4].reset(new GasPatch(s_patchContext, this, p3, p2, p6, p7));
793 	m_patches[5].reset(new GasPatch(s_patchContext, this, p8, p7, p6, p5));
794 
795 	GenerateTexture();
796 }
797 
Init()798 void GasGiant::Init()
799 {
800 	IniConfig cfg;
801 	cfg.Read(FileSystem::gameDataFiles, "configs/GasGiants.ini");
802 	// NB: limit the ranges of all values loaded from the file
803 	// NB: round to the nearest power of 2 for all texture sizes
804 	s_texture_size_small = ceil_pow2(Clamp(cfg.Int("texture_size_small", 16), 16, 64));
805 
806 	SplitData(cfg.String("texture_size_0"), s_texture_size_cpu[0], s_texture_size_gpu[0], s_noiseOctaves[0]);
807 	SplitData(cfg.String("texture_size_1"), s_texture_size_cpu[1], s_texture_size_gpu[1], s_noiseOctaves[1]);
808 	SplitData(cfg.String("texture_size_2"), s_texture_size_cpu[2], s_texture_size_gpu[2], s_noiseOctaves[2]);
809 	SplitData(cfg.String("texture_size_3"), s_texture_size_cpu[3], s_texture_size_gpu[3], s_noiseOctaves[3]);
810 	SplitData(cfg.String("texture_size_4"), s_texture_size_cpu[4], s_texture_size_gpu[4], s_noiseOctaves[4]);
811 
812 	s_initialCPUDelayTime = Clamp(cfg.Float("cpu_delay_time", 60.0f), 0.0f, 120.0f);
813 	s_initialGPUDelayTime = Clamp(cfg.Float("gpu_delay_time", 5.0f), 0.0f, 120.0f);
814 
815 	if (s_patchContext.Get() == nullptr) {
816 		s_patchContext.Reset(new GasPatchContext(127));
817 	}
818 	CreateRenderTarget(s_texture_size_gpu[Pi::detail.planets], s_texture_size_gpu[Pi::detail.planets]);
819 }
820 
Uninit()821 void GasGiant::Uninit()
822 {
823 	s_patchContext.Reset();
824 }
825 
826 //static
CreateRenderTarget(const Uint16 width,const Uint16 height)827 void GasGiant::CreateRenderTarget(const Uint16 width, const Uint16 height)
828 {
829 	/*	@fluffyfreak here's a rendertarget implementation you can use for oculusing and other things. It's pretty simple:
830 		 - fill out a RenderTargetDesc struct and call Renderer::CreateRenderTarget
831 		 - pass target to Renderer::SetRenderTarget to start rendering to texture
832 		 - set up viewport, clear etc, then draw as usual
833 		 - SetRenderTarget(0) to resume render to screen
834 		 - you can access the attached texture with GetColorTexture to use it with a material
835 		You can reuse the same target with multiple textures.
836 		In that case, leave the color format to NONE so the initial texture is not created, then use SetColorTexture to attach your own.
837 	*/
838 	Graphics::RenderStateDesc rsd;
839 	rsd.depthTest = false;
840 	rsd.depthWrite = false;
841 	rsd.blendMode = Graphics::BLEND_ALPHA;
842 	s_quadRenderState = Pi::renderer->CreateRenderState(rsd);
843 
844 	// Complete the RT description so we can request a buffer.
845 	// NB: we don't want it to create use a texture because we share it with the textured quad created above.
846 	Graphics::RenderTargetDesc rtDesc(
847 		width,
848 		height,
849 		Graphics::TEXTURE_NONE, // don't create a texture
850 		Graphics::TEXTURE_NONE, // don't create a depth buffer
851 		false);
852 	s_renderTarget = Pi::renderer->CreateRenderTarget(rtDesc);
853 }
854 
855 //static
SetRenderTargetCubemap(const Uint32 face,Graphics::Texture * pTexture,const bool unBind)856 void GasGiant::SetRenderTargetCubemap(const Uint32 face, Graphics::Texture *pTexture, const bool unBind /*= true*/)
857 {
858 	s_renderTarget->SetCubeFaceTexture(face, pTexture);
859 }
860 
861 //static
BeginRenderTarget()862 void GasGiant::BeginRenderTarget()
863 {
864 	Pi::renderer->SetRenderTarget(s_renderTarget);
865 }
866 
867 //static
EndRenderTarget()868 void GasGiant::EndRenderTarget()
869 {
870 	Pi::renderer->SetRenderTarget(nullptr);
871 }
872