/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "gpu/gpu_renderer.h" #include "core/terrain.h" #include "log/log.h" using namespace GS::GPU; // Terrain patch size. #define _PatchSize 64 // Terrain page count. #if __PLATFORM_ANDROID_NDK__ || __PLATFORM_IOS__ #define _PageCount 4 #else #define _PageCount 32 #endif //----------------------------------------------------------------------------- bool Renderer::CreateTerrainPatch() { // Allocate terrain cache. if (!terrain_patch_cache.Allocate(_PageCount)) __ERR__(__LOG_E__ << "Failed to allocate terrain cache.\n", false) // Compute page stride. size_t stride = 0; size_t vertex_offset = stride; stride += 3 * 4; // Vertex buffer (3x float). size_t normal_offset = stride; stride += (3 + 1) * 1; // Normal buffer (3x byte + 1 padding). size_t uv_offset = stride; stride += 2 * 4; // Large UV (2x float). // Setup default page layout. size_t byte_size = stride * (_PatchSize + 1) * (_PatchSize + 1); __LOG_H__ << "Setup terrain cache: " << terrain_patch_cache.GetCount() << " pages (" << uint(byte_size * terrain_patch_cache.GetCount()) << " bytes).\n"; terrain_patch_vtx.Allocate((uint)byte_size); if (char *p = terrain_patch_vtx) { Vector4 wp(0, 0, 0); for (int v = 0; v < (_PatchSize + 1); ++v) { for (int u = 0; u < (_PatchSize + 1); ++u) { float *pv = (float *)p; pv[0] = wp.x; pv[1] = 0; pv[2] = wp.z; p += stride; wp.x += 1.f; } wp.x = 0.f; wp.z += 1.f; } } // Allocate pages. for (uint n = 0; n < terrain_patch_cache.GetCount(); ++n) { DisplayList *patch = terrain_patch_cache[n].patch = NewDisplayList(); // Create the patch index buffer. patch->idx = NewVBO(); if (patch->idx->Create(6 * _PatchSize * _PatchSize * sizeof(ushort), VBO::Index, VBO::Static)) { SetIndexSource(patch->idx); if (ushort *p_idx = (ushort *)patch->idx->Map()) { for (int v = 0; v < _PatchSize; ++v) for (int u = 0; u < _PatchSize; ++u) { *p_idx++ = (ushort)(u + v * (_PatchSize + 1)); *p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1) + 1); *p_idx++ = (ushort)(u + v * (_PatchSize + 1) + 1); *p_idx++ = (ushort)(u + v * (_PatchSize + 1)); *p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1)); *p_idx++ = (ushort)(u + (v + 1) * (_PatchSize + 1) + 1); } patch->idx->Unmap(); patch->index_count = 6 * _PatchSize * _PatchSize; } SetIndexSource(NULL); } // Create the patch vertex buffer. patch->vertex_offset = vertex_offset; patch->normal_offset = normal_offset; patch->uv_offset[0] = uv_offset; patch->stride = stride; patch->vtx = NewVBO(); if (patch->vtx->Create(byte_size, VBO::Vertex, VBO::Dynamic)) { SetVertexSource(patch->vtx, patch->stride); patch->vtx->Update(terrain_patch_vtx); SetVertexSource(NULL, 0); } } return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void Renderer::InvalidateTerrainCache(int, int, int, int) { // Very naive invalidation that ignores the input region. for (uint n = 0; n < terrain_patch_cache.GetCount(); ++n) terrain_patch_cache[n].terrain = NULL; } void Renderer::DecayTerrainCache() { for (uint n = 0; n < terrain_patch_cache.GetCount(); ++n) if (terrain_patch_cache[n].score > 0) --terrain_patch_cache[n].score; } void Renderer::UploadTerrainPatchToPage(CachedTerrainPatch &page, const GS::Core::Item &item, const GS::Core::Patch &patch, const DrawContext &dc) { if (!patch.terrain || !page.patch) return; Core::Terrain &terrain = *patch.terrain; DisplayList &terrain_patch = *page.patch; float pixel_size = terrain.GetUnit(); Vector4 patch_scale(pixel_size * patch.decimation, 1, pixel_size * patch.decimation); #if 1 // Update patch CPU-side data. Core::Terrain::Attrib *psa = terrain.GetAttributesMap() + patch.v * terrain.GetHeightmapPitch() + patch.u; float *psh = terrain.GetHeightmap() + patch.v * terrain.GetHeightmapPitch() + patch.u; char *p = terrain_patch_vtx; Vector4 uv(0, 0, patch.v * pixel_size); for (int v = 0; v < (_PatchSize + 1); ++v) { uv.x = patch.u * pixel_size; Core::Terrain::Attrib *pa = psa; float *ph = psh; for (int u = 0; u < (_PatchSize + 1); ++u) { // Vertex. float *pv = (float *)(p + terrain_patch.vertex_offset); pv[1] = ph[0]; // Normal. char *pn = (char *)(p + terrain_patch.normal_offset); pn[0] = pa[0].nx; pn[1] = pa[0].ny; pn[2] = pa[0].nz; // UV. float *pu = (float *)(p + terrain_patch.uv_offset[0]); pu[0] = uv.x / terrain.GetWidth(); uv.x += patch_scale.x; pu[1] = uv.z / terrain.GetDepth(); p += terrain_patch.stride; pa += patch.decimation; ph += patch.decimation; } uv.z += patch_scale.z; psa += terrain.GetHeightmapPitch() * patch.decimation; psh += terrain.GetHeightmapPitch() * patch.decimation; } // Update VBO. SetVertexSource(terrain_patch.vtx, terrain_patch.stride); terrain_patch.vtx->Update(terrain_patch_vtx); // [EJ 9 Mar] the display list cache MUST be reset here otherwise an invalid // VBO reference will be used by the next draw calls for this patch. dls_cache.dls = NULL; #endif // Synchronize cache. page.terrain = patch.terrain; page.u = patch.u; page.v = patch.v; page.w = patch.w; page.h = patch.h; page.decimation = patch.decimation; } void Renderer::RenderTerrainPatch(const GS::Core::Patch &patch, const GS::Core::Item &item, const DrawContext &dc) { // Cache query. ++gpu_stats.terrain_page_query_count; DisplayList *dlist = NULL; CachedTerrainPatch *page = NULL; uint lowest_score = (uint)~0; for (uint n = 0; n < terrain_patch_cache.GetCount(); ++n) if (CachedTerrainPatch *c_page = &terrain_patch_cache[n]) { if ( (c_page->terrain == patch.terrain) && (c_page->u == patch.u) && (c_page->v == patch.v) && (c_page->w == patch.w) && (c_page->h == patch.h) && (c_page->decimation == patch.decimation) ) { dlist = c_page->patch; c_page->score++; // Increase score. break; } // Track the lowest scoring page. if (c_page->score <= lowest_score) { lowest_score = c_page->score; page = c_page; } } // Cache miss. if (!dlist) { ++gpu_stats.terrain_page_query_miss; if (!page || !page->patch) __ERRRAW__(__LOG_E__ << "No page to upload terrain patch.\n") // Unexpected... // Create new page. UploadTerrainPatchToPage(*page, item, patch, dc); dlist = page->patch; page->score = 1; // Reset score (+1 for being used). } // Draw patch scaled and translated on the GPU. float pixel_size = patch.terrain->GetUnit(); Vector4 patch_scale(pixel_size * patch.decimation, 1, pixel_size * patch.decimation); Matrix4 patch_matrix = Matrix4::TranslationMatrix(Vector4(patch.u * pixel_size - patch.terrain->GetWidth() * 0.5f, 0, patch.v * pixel_size - patch.terrain->GetDepth() * 0.5f)) * Matrix4::ScaleMatrix(patch_scale); SetWorldMatrix(item.GetMatrix() * patch_matrix); m_previous_world = item.GetPreviousMatrix() * patch_matrix; dls_cache.item = NULL; // Force matrices upload to program. // Draw. if (patch.terrain->render_data) { dlist->material = patch.terrain->render_data->material; DrawDisplayListCached(*dlist, item, dc); } } //-----------------------------------------------------------------------------