aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client/PS3/SPU_Tasks/LevelRenderer_FindNearestChunk/LevelRenderer_FindNearestChunk.cpp
blob: 3de394618e9de241aeb397e5ac4ade562514224f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/* SCE CONFIDENTIAL
PlayStation(R)3 Programmer Tool Runtime Library 430.001
* Copyright (C) 2007 Sony Computer Entertainment Inc.
* All Rights Reserved.
*/

/* common headers */
#include <stdint.h>
#include <stdlib.h>
#include <alloca.h>
#include <spu_intrinsics.h>
#include <cell/spurs.h>
#include <cell/dma.h>
#include <cell/spurs/job_queue.h>

#include "LevelRenderer_FindNearestChunk.h"
#include "..\Common\DmaData.h"
#include <vectormath/c/vectormath_aos_v.h>



// #define SPU_HEAPSIZE (128*1024)
// #define SPU_STACKSIZE (16*1024)
//
// CELL_SPU_LS_PARAM(128*1024, 16*1024);	// can't use #defines here as it seems to  create an asm instruction


static const bool sc_verbose = false;

CellSpursJobContext2* g_pSpursJobContext;


// The flag definitions
static const int    CHUNK_FLAG_COMPILED		= 0x01;
static const int    CHUNK_FLAG_DIRTY		= 0x02;
static const int    CHUNK_FLAG_EMPTY0		= 0x04;
static const int    CHUNK_FLAG_EMPTY1		= 0x08;
static const int	CHUNK_FLAG_EMPTYBOTH	= 0x0c;
static const int    CHUNK_FLAG_NOTSKYLIT	= 0x10;
static const int	CHUNK_FLAG_REF_MASK		= 0x07;
static const int	CHUNK_FLAG_REF_SHIFT	= 5;


bool inline clip(float *bb, float *frustum)
{
	for (int i = 0; i < 6; ++i, frustum += 4)
	{
		if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue;
		if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue;
		return false;
	}
	return true;
}

class PPUStoreArray
{
	static const int sc_cacheSize = 128;
	int m_localCache[128];
	int* m_pDataPPU;
	int m_cachePos;
	int m_ppuPos;

public:
	PPUStoreArray(uintptr_t pDataPPU) { m_pDataPPU = (int*)pDataPPU; m_cachePos = 0; m_ppuPos = 0;}

	void store(int val)
	{
		m_localCache[m_cachePos] = val;
		m_cachePos++;
		if(m_cachePos >= sc_cacheSize)
			flush();
	}

	void flush()
	{
		if(m_cachePos > 0)
		{
			// dma the local cache back to PPU and start again
// 			spu_print("DMAing %d bytes from 0x%08x(SPU) to 0x%08x(PPU)\n",(int)( m_cachePos*sizeof(int)), (int)m_localCache, (int)&m_pDataPPU[m_ppuPos]);
			DmaData_SPU::put(m_localCache, (uintptr_t)&m_pDataPPU[m_ppuPos], DmaData_SPU::roundUpDMASize(m_cachePos*sizeof(int)));
			m_ppuPos += m_cachePos;
			m_cachePos = 0;
		}
	}
	int getSize() { return m_ppuPos; }
};


bool LevelRenderer_FindNearestChunk_DataIn::MultiplayerChunkCache::getChunkEmpty(int lowerOffset, int upperOffset, int x, int y, int z)
{
	x>>=4;
	z>>=4;
	int ix = x + XZOFFSET;
	int iz = z + XZOFFSET;
	// Check we're in range of the stored level
	if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return false; // ( waterChunk ? waterChunk : emptyChunk );
	if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return false; //( waterChunk ? waterChunk : emptyChunk );
	int idx = ix * XZSIZE + iz;

// 	spu_print("grabbing pointer idx %d from 0x%08x", idx, (uintptr_t)&cache[idx]);
	uint32_t chunkPointer = DmaData_SPU::getValue32((uintptr_t)&cache[idx]);
// 	spu_print(" value - 0x%08x\n", chunkPointer);

	if( chunkPointer == NULL )
	{
		return false;
	}
	else
	{
		CompressedTileStorage blocks;
		uintptr_t pBlocks;
		// using a class structure offset here as we don't want to be compiling LevelChunk on SPU
		int chunkY = y;
		if( y >= 128 )
		{
			pBlocks = DmaData_SPU::getValue32((uintptr_t)(chunkPointer+upperOffset));
			chunkY -= 128;
		}
		else
		{
			pBlocks = DmaData_SPU::getValue32((uintptr_t)(chunkPointer+lowerOffset));
		}
		DmaData_SPU::getAndWaitUnaligned(&blocks, pBlocks, sizeof(CompressedTileStorage));
		return blocks.isRenderChunkEmpty(chunkY);
	}
}


bool LevelRenderer_FindNearestChunk_DataIn::CompressedTileStorage::isRenderChunkEmpty(int y)	// y == 0, 16, 32... 112 (representing a 16 byte range)
{
	int blockIdx;
	unsigned short *blockIndices = (unsigned short *)indicesAndData;

	for( int x = 0; x < 16; x += 4 )
	{
		for( int z = 0; z < 16; z += 4 )
		{
			getBlock(&blockIdx, x, y, z);
			uint16_t comp;
			comp = DmaData_SPU::getValue16((uintptr_t)&blockIndices[blockIdx]);
			if( comp != 0x0007 ) return false;
			comp = DmaData_SPU::getValue16((uintptr_t)&blockIndices[blockIdx+1]);
			if( comp != 0x0007 ) return false;
			comp = DmaData_SPU::getValue16((uintptr_t)&blockIndices[blockIdx+2]);
			if( comp != 0x0007 ) return false;
			comp = DmaData_SPU::getValue16((uintptr_t)&blockIndices[blockIdx+3]);
			if( comp != 0x0007 ) return false;
		}
	}
	return true;
}


void LevelRenderer_FindNearestChunk_DataIn::findNearestChunk()
{
 	unsigned char* globalChunkFlags = (unsigned char*)alloca(numGlobalChunks);		// 164K !!!
 	DmaData_SPU::getAndWait(globalChunkFlags, (uintptr_t)pGlobalChunkFlags, sizeof(unsigned char)*numGlobalChunks);


	nearChunk = NULL;			// Nearest chunk that is dirty
	veryNearCount = 0;
	int minDistSq = 0x7fffffff;		// Distances to this chunk


	// Find nearest chunk that is dirty
	for( int p = 0; p < 4; p++ )
	{
		// It's possible that the localplayers member can be set to NULL on the main thread when a player chooses to exit the game
		// So take a reference to the player object now. As it is a std::shared_ptr it should live as long as we need it
		PlayerData* player = &playerData[p];
		if( player->bValid == NULL ) continue;
		if( chunks[p] == NULL ) continue;
		if( level[p] == NULL ) continue;
		if( chunkLengths[p] != xChunks * zChunks * CHUNK_Y_COUNT ) continue;
		int px = (int)player->x;
		int py = (int)player->y;
		int pz = (int)player->z;

		ClipChunk clipChunk[512];

		for( int z = 0; z < zChunks; z++ )
		{
			uintptr_t ClipChunkX_PPU = (uintptr_t)&chunks[p][(z * yChunks + 0) * xChunks + 0];
			DmaData_SPU::getAndWait(&clipChunk[0], ClipChunkX_PPU, sizeof(ClipChunk) * xChunks*CHUNK_Y_COUNT);
			for( int y = 0; y < CHUNK_Y_COUNT; y++ )
			{
				for( int x = 0; x < xChunks; x++ )
				{
					ClipChunk *pClipChunk = &clipChunk[(y) * xChunks + x];

					// Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves
					int xd = pClipChunk->xm - px;
					int yd = pClipChunk->ym - py;
					int zd = pClipChunk->zm - pz;
					int distSq = xd * xd + yd * yd + zd * zd;
					int distSqWeighted = xd * xd + yd * yd * 4 + zd * zd;  // Weighting against y to prioritise things in same x/z plane as player first

					if( globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY )
					{
						if( (!onlyRebuild) ||
							globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_COMPILED ||
							( distSq < 20 * 20 ) )	// Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data
						{
							// Is this chunk nearer than our nearest?
							if( distSqWeighted < minDistSq )
							{
								// At this point we've got a chunk that we would like to consider for rendering, at least based on its proximity to the player(s).
								// Its *quite* quick to generate empty render data for render chunks, but if we let the rebuilding do that then the after rebuilding we will have
								// to start searching for the next nearest chunk from scratch again. Instead, its better to detect empty chunks at this stage, flag them up as not dirty
								// (and empty), and carry on. The levelchunk's isRenderChunkEmpty method can be quite optimal as it can make use of the chunk's data compression to detect
								// emptiness without actually testing as many data items as uncompressed data would.
 								Chunk chunk;
								DmaData_SPU::getAndWait(&chunk, (uintptr_t)pClipChunk->chunk, sizeof(Chunk));
								if(!multiplayerChunkCache[p].getChunkEmpty(lowerOffset, upperOffset, chunk.x, y*16, chunk.z))
 								{
									uintptr_t ClipChunkPPU = (uintptr_t)&chunks[p][(z * yChunks + y) * xChunks + x];
									nearChunk = (ClipChunk*)ClipChunkPPU;
 									minDistSq = distSqWeighted;
 								}
 								else
 								{
 									globalChunkFlags[ pClipChunk->globalIdx ] &= ~CHUNK_FLAG_DIRTY;
  									globalChunkFlags[ pClipChunk->globalIdx ] |= CHUNK_FLAG_EMPTYBOTH;
 								}
							}

							if( distSq < 20 * 20 )
							{
								veryNearCount++;
							}
						}
					}
				}
			}
		}
	}

 	DmaData_SPU::putAndWait(globalChunkFlags, (uintptr_t)pGlobalChunkFlags, sizeof(unsigned char)*numGlobalChunks);

}




void cellSpursJobQueueMain(CellSpursJobContext2 *pContext, CellSpursJob256 *pJob)
{
	// 	CellSpursTaskId idTask = cellSpursGetTaskId();
	unsigned int idSpu = cellSpursGetCurrentSpuId();

	if(sc_verbose)
		spu_print("LevelRenderer_cull [SPU#%u] start\n", idSpu);

	g_pSpursJobContext = pContext;
	uint32_t eaDataIn = pJob->workArea.userData[0];
// 	uint32_t eaDataOut =pJob->workArea.userData[1];

	LevelRenderer_FindNearestChunk_DataIn dataIn;
	DmaData_SPU::getAndWait(&dataIn, eaDataIn, sizeof(LevelRenderer_FindNearestChunk_DataIn));

	dataIn.findNearestChunk();

 	DmaData_SPU::putAndWait(&dataIn, eaDataIn, sizeof(LevelRenderer_FindNearestChunk_DataIn));


	if(sc_verbose)
		spu_print("LevelRenderer_cull [SPU#%u] exit\n", idSpu);
}