OpenCPN Partial API docs
Loading...
Searching...
No Matches
glTexCache.cpp
1/******************************************************************************
2 *
3 * Project: OpenCPN
4 * Authors: David Register
5 * Sean D'Epagnier
6 *
7 ***************************************************************************
8 * Copyright (C) 2014 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 ***************************************************************************
25 */
26
27#include <wx/wxprec.h>
28#include <wx/tokenzr.h>
29#include <wx/filename.h>
30#include <wx/wx.h>
31
32#include <stdint.h>
33
34#include "dychart.h"
35
36#include "config.h"
37
38#include "viewport.h"
39#include "glTexCache.h"
40#include "glTextureDescriptor.h"
41
42#include "chcanv.h"
43#include "glChartCanvas.h"
44#include "Quilt.h"
45#include "chartbase.h"
46#include "chartimg.h"
47#include "chartdb.h"
48#include "OCPNPlatform.h"
49#include "mipmap/mipmap.h"
50
51#ifndef GL_ETC1_RGB8_OES
52#define GL_ETC1_RGB8_OES 0x8D64
53#endif
54
55#include "squish.h"
56#include "lz4.h"
57#include "lz4hc.h"
58
59// Correct some deficincies in MacOS OpenGL include files
60#ifdef __WXOSX__
61typedef void (*PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers);
62typedef void (*PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
63typedef void (*PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers);
64typedef void (*PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname,
65 GLint *params);
66typedef void (*PFNGLDELETERENDERBUFFERSEXTPROC)(GLsizei n,
67 const GLuint *renderbuffers);
68typedef void (*PFNGLDELETEFRAMEBUFFERSEXTPROC)(GLsizei n,
69 const GLuint *framebuffers);
70typedef void (*PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level,
71 GLint xoffset, GLsizei width,
72 GLenum format,
73 GLsizei imageSize,
74 const GLvoid *data);
75typedef void (*PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level,
76 GLvoid *img);
77typedef GLenum (*PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)(GLenum target);
78typedef void (*PFNGLBINDRENDERBUFFEREXTPROC)(GLenum target,
79 GLuint renderbuffer);
80typedef void (*PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size,
81 const GLvoid *data, GLenum usage);
82typedef void (*PFNGLGENFRAMEBUFFERSEXTPROC)(GLsizei n, GLuint *framebuffers);
83typedef void (*PFNGLGENRENDERBUFFERSEXTPROC)(GLsizei n, GLuint *renderbuffers);
84typedef void (*PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)(GLenum target,
85 GLenum attachment,
86 GLenum textarget,
87 GLuint texture, GLint level);
88typedef void (*PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level,
89 GLenum internalformat,
90 GLsizei width, GLsizei height,
91 GLint border, GLsizei imageSize,
92 const GLvoid *data);
93typedef void (*PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)(GLenum target,
94 GLenum attachment,
95 GLenum renderbuffertarget,
96 GLuint renderbuffer);
97typedef void (*PFNGLRENDERBUFFERSTORAGEEXTPROC)(GLenum target,
98 GLenum internalformat,
99 GLsizei width, GLsizei height);
100typedef void (*PFNGLBINDFRAMEBUFFEREXTPROC)(GLenum target, GLuint framebuffer);
101#endif
102
103extern long g_tex_mem_used;
104extern int g_mipmap_max_level;
105extern GLuint g_raster_format;
106extern int g_memCacheLimit;
107
108extern ColorScheme global_color_scheme;
109
110extern ChartDB *ChartData;
111extern ocpnGLOptions g_GLOptions;
112
113extern int g_tile_size;
114
115extern bool GetMemoryStatus(int *mem_total, int *mem_used);
116
117extern wxString CompressedCachePath(wxString path);
118extern glTextureManager *g_glTextureManager;
119
120// CatalogEntry implementation
121CatalogEntry::CatalogEntry() {}
122
123CatalogEntry::~CatalogEntry() {}
124
125CatalogEntry::CatalogEntry(int level, int x0, int y0, ColorScheme colorscheme) {
126 k.mip_level = level;
127 k.x = x0;
128 k.y = y0;
129 k.tcolorscheme = colorscheme;
130}
131
132int CatalogEntry::GetSerialSize() { return CATALOG_ENTRY_SERIAL_SIZE; }
133
134void CatalogEntry::Serialize(unsigned char *t) {
135 uint32_t *p = (uint32_t *)t;
136
137 *p++ = k.mip_level;
138 *p++ = k.x;
139 *p++ = k.y;
140 *p++ = k.tcolorscheme;
141 *p++ = v.texture_offset;
142 *p++ = v.compressed_size;
143}
144
145void CatalogEntry::DeSerialize(unsigned char *t) {
146 uint32_t *p = (uint32_t *)t;
147
148 k.mip_level = *p++;
149 k.x = *p++;
150 k.y = *p++;
151 k.tcolorscheme = (ColorScheme)*p++;
152 v.texture_offset = *p++;
153 v.compressed_size = *p++;
154}
155
156// glTexFactory Implementation
157enum TextureDataType { COMPRESSED_BUFFER_OK, MAP_BUFFER_OK };
158
159glTexFactory::glTexFactory(ChartBase *chart, int raster_format) {
160 // m_pchart = chart;
161 n_catalog_entries = 0;
162 m_catalog_offset = sizeof(CompressedCacheHeader);
163 wxDateTime ed = chart->GetEditionDate();
164 m_chart_date_binary = (uint32_t)ed.IsValid() ? ed.GetTicks() : 0;
165 m_chartfile_date_binary = ::wxFileModificationTime(chart->GetFullPath());
166 m_chartfile_size =
167 (uint32_t)wxFileName::GetSize(chart->GetFullPath()).GetLo();
168 m_ChartPath = chart->GetFullPath();
169
170 m_CompressedCacheFilePath = CompressedCachePath(chart->GetFullPath());
171 m_hdrOK = false;
172 m_catalogOK = false;
173 m_newCatalog = true;
174
175 m_catalogCorrupted = false;
176
177 m_fs = 0;
178 m_LRUtime = 0;
179 m_ntex = 0;
180 m_tiles = NULL;
181 for (int i = 0; i < N_COLOR_SCHEMES; i++) {
182 for (int j = 0; j < MAX_TEX_LEVEL; j++) {
183 m_cache[i][j] = NULL;
184 }
185 }
186 // Initialize the TextureDescriptor array
187 ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(chart);
188
189 if (!pBSBChart) return;
190
191 m_size_X = pBSBChart->GetSize_X();
192 m_size_Y = pBSBChart->GetSize_Y();
193
194 // Calculate the number of textures needed
195 m_tex_dim = g_GLOptions.m_iTextureDimension;
196 m_nx_tex = (m_size_X / m_tex_dim) + ((m_size_X % m_tex_dim) == 0 ? 0 : 1);
197 m_ny_tex = (m_size_Y / m_tex_dim) + ((m_size_Y % m_tex_dim) == 0 ? 0 : 1);
198
199 m_stride = m_nx_tex;
200 m_ntex = m_nx_tex * m_ny_tex;
201 m_td_array =
202 (glTextureDescriptor **)calloc(m_ntex, sizeof(glTextureDescriptor *));
203
204 m_prepared_projection_type = 0;
205}
206
207glTexFactory::~glTexFactory() {
208 delete m_fs;
209
210 PurgeBackgroundCompressionPool();
211 DeleteAllTextures();
212 DeleteAllDescriptors();
213
214 for (int i = 0; i < N_COLOR_SCHEMES; i++) {
215 for (int j = 0; j < MAX_TEX_LEVEL; j++) {
216 CatalogEntryValue *v = m_cache[i][j];
217 if (v) {
218 free(v);
219 }
220 }
221 }
222
223 free(m_td_array); // array is empty
224
225 if (m_tiles)
226 for (int i = 0; i < m_ntex; i++) delete m_tiles[i];
227 delete[] m_tiles;
228}
229
230glTextureDescriptor *glTexFactory::GetpTD(wxRect &rect) {
231 int array_index = ArrayIndex(rect.x, rect.y);
232 return m_td_array[array_index];
233}
234
235bool glTexFactory::OnTimer() {
236 for (int i = 0; i < m_ntex; i++) {
237 glTextureDescriptor *ptd = m_td_array[i];
238 // sometimes compressed data is produced but by the time
239 // it arrives it is no longer needed, so with a timeout
240 // of 5 seconds free this memory to avoid ram use buildup
241 if (ptd && ptd->compdata_ticks) {
242 ptd->compdata_ticks--;
243 ptd->FreeComp();
244 }
245 }
246
247#if 0 // this is proven unreliable and slow
248 // if we have the data in the catalog of level 0 or doubly compressed
249 // for an entire row of tiles, then we can free rows from the linebuffer
250 if(g_GLOptions.m_bTextureCompression) {
251 ChartBase *pChart = ChartData->OpenChartFromDB( m_ChartPath, FULL_INIT );
252 ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB*>( pChart );
253
254 if(pBSBChart) {
255 for(int y = 0; y<m_ny_tex; y++) {
256 int dim = g_GLOptions.m_iTextureDimension;
257
258 if(!pBSBChart->HaveLineCacheRow(y*dim))
259 continue;
260
261 for(int x = 0; x<m_nx_tex; x++) {
262 int i = ArrayIndex(x, y);
263 glTextureDescriptor *ptd = m_td_array[i];
264
265 if( !ptd )
266 goto keeplines;
267
268 if( ptd->compcomp_array[0] )
269 continue; // ok
270
271 CatalogEntryValue *p = GetCacheEntryValue(0, x*dim, y*dim, ptd->m_colorscheme);
272 if(!p)
273 goto keeplines;
274 }
275
276 pBSBChart->FreeLineCacheRows(y*dim, (y+1)*dim);
277 }
278 keeplines:;
279 }
280 }
281#endif
282
283 // write doubly compressed data to disk
284 if (g_GLOptions.m_bTextureCompressionCaching)
285 for (int i = 0; i < m_ntex; i++) {
286 glTextureDescriptor *ptd = m_td_array[i];
287 if (ptd && ptd->IsCompCompArrayComplete(0)) {
288 int dim = g_GLOptions.m_iTextureDimension;
289 UpdateCacheAllLevels(wxRect(ptd->x, ptd->y, dim, dim),
290 ptd->m_colorscheme, ptd->compcomp_array,
291 ptd->compcomp_size);
292
293 // no longer need to store the compressed compressed data
294 ptd->FreeCompComp();
295 // return true;
296 }
297 }
298
299 return false;
300}
301
302#if 0
303#ifdef __OCPN__ANDROID__
304 // delete any uncompressed textures if texture memory is more than 30
305 // on android?? Maybe this should be removed now
306 bool bGLMemCrunch = g_tex_mem_used > 30/*g_GLOptions.m_iTextureMemorySize*/ * 1024 * 1024;
307
308 if( bGLMemCrunch ){
309 for(wxTextureListNode *node = m_texture_list.GetFirst(); node;
310 node = node->GetNext()) {
311 glTextureDescriptor *ptd = node->GetData();
312 if(ptd->nGPU_compressed == GPU_TEXTURE_UNCOMPRESSED){
313 DeleteSingleTexture(ptd);
314 }
315 }
316 }
317#endif
318#endif
319
320void glTexFactory::AccumulateMemStatistics(int &map_size, int &comp_size,
321 int &compcomp_size) {
322 for (int i = 0; i < m_ntex; i++) {
323 glTextureDescriptor *ptd = m_td_array[i];
324 if (ptd) {
325 map_size += ptd->GetMapArrayAlloc();
326 comp_size += ptd->GetCompArrayAlloc();
327 compcomp_size += ptd->GetCompCompArrayAlloc();
328 }
329 }
330}
331
332void glTexFactory::DeleteTexture(const wxRect &rect) {
333 // Is this texture tile defined?
334 int array_index = ArrayIndex(rect.x, rect.y);
335 glTextureDescriptor *ptd = m_td_array[array_index];
336
337 if (ptd && ptd->tex_name > 0) {
338 DeleteSingleTexture(ptd);
339 }
340}
341
342void glTexFactory::DeleteAllTextures(void) {
343 // iterate over all the textures presently loaded
344 // and delete the OpenGL texture from the GPU
345 // but keep the private texture descriptor for now
346
347 for (int i = 0; i < m_ntex; i++) {
348 glTextureDescriptor *ptd = m_td_array[i];
349
350 if (ptd) {
351 // if(ptd->tex_name && bthread_debug)
352 // printf("DAT::Delete Texture %d resulting
353 // g_tex_mem_used, mb: %ld\n", ptd->tex_name,
354 // g_tex_mem_used/(1024 * 1024));
355
356 DeleteSingleTexture(ptd);
357 }
358 }
359}
360
361void glTexFactory::DeleteSomeTextures(long target) {
362 // iterate over all the textures presently loaded
363 // and delete the OpenGL texture from the GPU
364 // until the target g_tex_mem_used is reached
365 // but keep the private texture descriptor for now
366
367 for (int i = 0; i < m_ntex; i++) {
368 glTextureDescriptor *ptd = m_td_array[i];
369
370 if (ptd) {
371 // if(ptd->tex_name && bthread_debug)
372 // printf("DSoT::Delete Some Texture %d resulting
373 // g_tex_mem_used, mb: %ld\n", ptd->tex_name,
374 // g_tex_mem_used/(1024 * 1024));
375
376 if (ptd->tex_name) DeleteSingleTexture(ptd);
377
378 if (g_tex_mem_used <= target) break;
379 }
380 }
381}
382
383void glTexFactory::FreeSome(long target) {
384 for (int i = 0; i < m_ntex; i++) {
385 glTextureDescriptor *ptd = m_td_array[i];
386
387 if (ptd) ptd->FreeMap();
388 }
389}
390
391void glTexFactory::DeleteAllDescriptors(void) {
392 // iterate over all the texture descriptors
393
394 for (int i = 0; i < m_ntex; i++) {
395 glTextureDescriptor *ptd = m_td_array[i];
396 delete ptd;
397 m_td_array[i] = 0;
398 }
399}
400
401bool glTexFactory::BackgroundCompressionAsJob() const {
402 return g_glTextureManager->AsJob(m_ChartPath);
403}
404
405void glTexFactory::PurgeBackgroundCompressionPool() {
406 // Purge the "todo" list, and allow any running jobs to complete normally
407 g_glTextureManager->PurgeJobList(m_ChartPath);
408}
409
410void glTexFactory::DeleteSingleTexture(glTextureDescriptor *ptd) {
411 if (!ptd->tex_name) return;
412
413 g_tex_mem_used -= ptd->tex_mem_used;
414 ptd->level_min = g_mipmap_max_level + 1; // default, nothing loaded
415
416 glDeleteTextures(1, &ptd->tex_name);
417 ptd->tex_name = 0;
418 ptd->tex_mem_used = 0;
419 ptd->nGPU_compressed = GPU_TEXTURE_UNKNOWN;
420}
421
422void glTexFactory::ArrayXY(wxRect *r, int index) const {
423 r->y = (index / m_stride) * m_tex_dim;
424 r->x = (index - ((r->y / m_tex_dim) * m_stride)) * m_tex_dim;
425}
426
427CatalogEntryValue *glTexFactory::GetCacheEntryValue(int level, int x, int y,
428 ColorScheme color_scheme) {
429 if (level < 0 || level >= MAX_TEX_LEVEL) return 0;
430
431 // Look in the cache
432 LoadCatalog();
433
434 CatalogEntryValue *v = m_cache[color_scheme][level];
435 if (v == 0) return 0;
436
437 int array_index = ArrayIndex(x, y);
438 if (array_index >= m_ntex) return 0;
439
440 CatalogEntryValue *r = &v[array_index];
441 if (r->compressed_size == 0) return 0;
442
443 return r;
444}
445
446bool glTexFactory::IsLevelInCache(int level, const wxRect &rect,
447 ColorScheme color_scheme) {
448 bool b_ret = false;
449
450 if (g_GLOptions.m_bTextureCompression &&
451 g_GLOptions.m_bTextureCompressionCaching) {
452 // Search for the requested texture
453 if (GetCacheEntryValue(level, rect.x, rect.y, color_scheme) != 0)
454 b_ret = true;
455 }
456
457 return b_ret;
458}
459
460glTextureDescriptor *glTexFactory::GetOrCreateTD(const wxRect &rect) {
461 int array_index = ArrayIndex(rect.x, rect.y);
462 if (!m_td_array[array_index]) {
464
465 p->x = rect.x;
466 p->y = rect.y;
467 p->level_min = g_mipmap_max_level + 1; // default, nothing loaded
468 p->m_colorscheme = global_color_scheme;
469 m_td_array[array_index] = p;
470 }
471 return m_td_array[array_index];
472}
473
474static void CreateTexture(GLuint &tex_name, bool b_use_mipmaps) {
475 glGenTextures(1, &tex_name);
476
477 // printf("gentex %d rect: %d %d index %d\n", ptd->tex_name,
478 // rect.x, rect.y, array_index);
479 glBindTexture(GL_TEXTURE_2D, tex_name);
480
481 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
482 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
483 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
484
485 if (b_use_mipmaps)
486 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
487 GL_LINEAR_MIPMAP_LINEAR);
488 else
489 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
490
491#ifdef __OCPN__ANDROID__
492 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
493 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
494#endif
495}
496
497bool glTexFactory::BuildTexture(glTextureDescriptor *ptd, int base_level,
498 const wxRect &rect) {
499 bool busy_shown = false;
500
501 // the quality is only slightly worse because linear_mipmap_linear
502 // is impossible, but still replace the texture data with the
503 // correct level data and using only texture level 0
504
505#ifdef ocpnUSE_GLES
506 bool b_use_compressed_mipmaps = false;
507 bool b_use_uncompressed_mipmaps = false;
508#else
509 bool b_use_compressed_mipmaps = true; // best possible quality
510 // don't use uncompressed mipmaps as they are temporary
511 // we upload only the correct level so the image quality is good anyways
512 bool b_use_uncompressed_mipmaps = !g_GLOptions.m_bTextureCompression;
513#endif
514
515 // on systems with little memory we can go up a mipmap level
516 // here so that the uncompressed size without mipmaps (level+1)
517 // is nearly the compressed size with all the compressed mipmaps
518 // this way we won't require 5x more video memory than normal while we
519 // are generating the compressed textures, when the cache is complete the
520 // image becomes clearer as it is replaces with the higher resolution
521 // compressed version
522 bool b_lowmem =
523 false; // maybe instead decide based on how much texture memory we have
524#ifdef ocpnUSE_GLES
525 b_lowmem = g_GLOptions.m_bTextureCompression;
526#endif
527 if (g_GLOptions.m_bTextureCompression &&
528 ptd->nGPU_compressed == GPU_TEXTURE_UNCOMPRESSED) {
529 // if compressed data became available we blow away the texture
530 if (ptd->comp_array[base_level]) DeleteSingleTexture(ptd);
531 }
532
533 // we are done if the data is already in the texture
534 if (base_level == ptd->level_min) return false;
535
536 if (base_level > ptd->level_min) {
537 // if we already have the mipmaps then we can return,
538 // but if we need memory we should free the texture and
539 // re-upload just the higher levels needed
540 bool b_use_mipmaps = ptd->nGPU_compressed == GPU_TEXTURE_COMPRESSED
541 ? b_use_compressed_mipmaps
542 : b_use_uncompressed_mipmaps;
543 if (b_use_mipmaps) {
544 double factor = 0.5; // we should free uncompressed textures earliest
545 bool bGLMemCrunch =
546 g_tex_mem_used >
547 (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) * factor;
548 if (!bGLMemCrunch) return false; // we already have the data in vram
549 }
550 }
551
552 int status = GetTextureLevel(ptd, rect, base_level, ptd->m_colorscheme);
553
554 bool b_use_mipmaps = COMPRESSED_BUFFER_OK == status
555 ? b_use_compressed_mipmaps
556 : b_use_uncompressed_mipmaps;
557
558 DeleteSingleTexture(ptd);
559 CreateTexture(ptd->tex_name, b_use_mipmaps);
560 ptd->nGPU_compressed = COMPRESSED_BUFFER_OK == status
561 ? GPU_TEXTURE_COMPRESSED
562 : GPU_TEXTURE_UNCOMPRESSED;
563
564 if (COMPRESSED_BUFFER_OK == status) {
565 int texture_level = 0;
566 for (int level = base_level; level < ptd->level_min; level++) {
567 int size = TextureTileSize(level, true);
568 int status = GetTextureLevel(ptd, rect, level, ptd->m_colorscheme);
569 int dim = TextureDim(level);
570 glCompressedTexImage2D(GL_TEXTURE_2D, texture_level, g_raster_format,
571 dim, dim, 0, size, ptd->comp_array[level]);
572
573 ptd->tex_mem_used += size;
574 g_tex_mem_used += size;
575 texture_level++;
576
577 if (!b_use_mipmaps) break;
578 }
579
580 // Free bitmap memory that has already been uploaded to the GPU
581 ptd->FreeMap();
582 ptd->FreeComp();
583 } else { // COMPRESSED_BUFFER_OK == status
584 if (m_newCatalog) {
585 // it's an empty catalog or it's not used, odds it's going to be slow
586 OCPNPlatform::ShowBusySpinner();
587 busy_shown = true;
588 m_newCatalog = false;
589 }
590
591 // This level has not been compressed yet, and is not in the cache
592#if 1 // perhaps we should eliminate this case
593 // and build compressed fxt1 textures one per tick
594 if (GL_COMPRESSED_RGB_FXT1_3DFX == g_raster_format &&
595 g_GLOptions.m_bTextureCompression) {
596 // this version avoids re-uploading the data
597 g_glTextureManager->ScheduleJob(this, rect, base_level, true, false, true,
598 true);
599 ptd->FreeMap();
600 ptd->nGPU_compressed = GPU_TEXTURE_COMPRESSED;
601 b_use_mipmaps = b_use_compressed_mipmaps;
602 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
603 GL_LINEAR_MIPMAP_LINEAR);
604 } else
605#endif
606 {
607 int uc_base_level = base_level;
608 if (b_lowmem) uc_base_level++;
609 int texture_level = 0;
610 for (int level = uc_base_level; level < ptd->level_min + b_lowmem;
611 level++) {
612 int status = GetTextureLevel(ptd, rect, level, ptd->m_colorscheme);
613 int dim = TextureDim(level);
614 glTexImage2D(GL_TEXTURE_2D, texture_level, GL_RGB, dim, dim, 0,
615 FORMAT_BITS, GL_UNSIGNED_BYTE, ptd->map_array[level]);
616 int size = TextureTileSize(level, false);
617 ptd->tex_mem_used += size;
618 g_tex_mem_used += size;
619 texture_level++;
620
621 if (!b_use_mipmaps) break;
622 }
623 }
624 }
625
626 ptd->level_min = base_level;
627
628 // free all mipmaps more than a level less than this
629 for (int i = 0; i < base_level - 1; i++) {
630 free(ptd->map_array[i]);
631 ptd->map_array[i] = 0;
632 }
633
634 if (busy_shown) OCPNPlatform::HideBusySpinner();
635
636 return true;
637}
638
639bool glTexFactory::PrepareTexture(int base_level, const wxRect &rect,
640 ColorScheme color_scheme, int mem_used) {
641 glTextureDescriptor *ptd = NULL;
642
643 try {
644 ptd = GetOrCreateTD(rect);
645
646 ptd->m_colorscheme = color_scheme;
647
648 // glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); // why?
649
650 if (!BuildTexture(ptd, base_level, rect))
651 glBindTexture(GL_TEXTURE_2D, ptd->tex_name);
652
653 // should we schedule compression?
654 if (g_GLOptions.m_bTextureCompression &&
655 ptd->nGPU_compressed == GPU_TEXTURE_UNCOMPRESSED) {
656 // scheduling at base_level reduces vram usage but is slower overall
657 // probably shouldn't be used for caching until it can cache each level
658 g_glTextureManager->ScheduleJob(this, rect, 0 /*base_level*/, true, false,
659 true, false);
660 if (GL_COMPRESSED_RGB_FXT1_3DFX == g_raster_format)
661 glBindTexture(GL_TEXTURE_2D, ptd->tex_name); // reset texture binding
662
663 // Free the map in ram
664 ptd->FreeMap();
665 }
666
667#if 0
668 /* this is not a good place to call GetMemoryStatus
669 function as implemented takes for me 1 millisecond to execute.
670 In the worst case zoomed out charts can reach thousands of textures and several seconds to render one frame instead of 20-30fps disabling this here
671
672 Memory is already freed in glTextureManager::FactoryCrunch(double factor)
673 so the below is probably not needed.*/
674
675 // If global memory is getting short, we can crunch here.
676 // All mipmaps >= ptd->level_min have been uploaded to the GPU,
677 // so there is no reason to save the bits forever.
678 // Of course, this means that if the texture is deleted elsewhere, then the bits will need to be
679 // regenerated. The price to pay for memory limits....
680 if (g_memCacheLimit > 0) {
681 // GetMemoryStatus is slow on linux
682 if(mem_used > g_memCacheLimit * 7 / 10)
683 ptd->FreeMap();
684
685 if(mem_used > g_memCacheLimit * 9 / 10)
686 ptd->FreeAll();
687 }
688#endif
689
690 // g_Platform->HideBusySpinner();
691
692 return true;
693 } // try
694
695 catch (...) {
696 // Clean up
697 ptd->FreeAll();
698 DeleteSingleTexture(ptd);
699 return false;
700 }
701}
702
703void glTexFactory::PrepareTiles(const ViewPort &vp, bool use_norm_vp,
704 ChartBase *chart) {
705 ChartBaseBSB *pChartBSB = dynamic_cast<ChartBaseBSB *>(chart);
706 if (!pChartBSB) return;
707
708 // detect changing north/south polar
709 if (vp.m_projection_type == PROJECTION_POLAR) {
710 bool north = vp.clat > 0;
711 if (m_north != north) m_prepared_projection_type = 0;
712 m_north = north;
713 }
714
715 if (vp.m_projection_type == m_prepared_projection_type) return;
716
717 m_prepared_projection_type = vp.m_projection_type;
718
719 double native_scale;
720
721 native_scale = pChartBSB->GetNativeScale();
722
723 if (m_tiles)
724 for (int i = 0; i < m_ntex; i++) delete m_tiles[i];
725 delete[] m_tiles;
726 m_tiles = new glTexTile *[m_ntex];
727
728 int tex_dim = g_GLOptions.m_iTextureDimension;
729
730 // split cells for accuracy, much more with larger charts, and toward poles
731 // depending on projection of viewport and chart
732 // This is a very simplistic algorithm to determine split count, could be
733 // greatly improved
734
735 double xsplits, ysplits;
736 switch (vp.m_projection_type) {
737 case PROJECTION_POLAR:
738 case PROJECTION_STEREOGRAPHIC:
739 case PROJECTION_ORTHOGRAPHIC:
740 case PROJECTION_GNOMONIC:
741 case PROJECTION_POLYCONIC:
742 xsplits = native_scale / 1000000000.0 * tex_dim; // todo: fix this
743 // xsplits /= (1 << base_level); // split less zoomed out
744
745 // split more near poles
746 if (vp.m_projection_type == PROJECTION_ORTHOGRAPHIC) {
747 Extent e;
748 pChartBSB->GetChartExtent(&e);
749 xsplits = xsplits * wxMax(fabsf(e.NLAT), fabsf(e.SLAT)) / 90;
750 }
751
752 xsplits = round(xsplits);
753 ysplits = 2 * xsplits;
754
755 xsplits = wxMin(wxMax(xsplits, 1), 8);
756 ysplits = wxMin(wxMax(ysplits, 1), 8);
757 break;
758 case PROJECTION_EQUIRECTANGULAR:
759 // needed for skewed charts?
760 // xsplits = ysplits = 4;
761 // break;
762 default:
763 xsplits = ysplits =
764 1; // TODO: is this good enough in all cases to reproject
765 // non-mercator charts or even SM_ECC mercator charts in all cases?
766 }
767
768 ViewPort nvp;
769 if (use_norm_vp) {
770 pChartBSB->chartpix_to_latlong(m_size_X / 2, m_size_Y / 2, &m_clat,
771 &m_clon);
772 nvp = glChartCanvas::NormalizedViewPort(vp, m_clat, m_clon);
773 }
774
775 // Using a 2D loop, iterate thru the texture tiles of the chart
776 wxRect rect;
777 rect.y = 0;
778 for (int i = 0; i < m_ny_tex; i++) {
779 rect.height = tex_dim;
780 rect.x = 0;
781 for (int j = 0; j < m_nx_tex; j++) {
782 rect.width = tex_dim;
783
784 glTexTile *tile = m_tiles[i * m_nx_tex + j] = new glTexTile;
785 tile->rect = rect;
786
787 double lat, lon;
788 float ll[8];
789 int x[4] = {rect.x, rect.x, rect.x + rect.width, rect.x + rect.width};
790 int y[4] = {rect.y + rect.height, rect.y, rect.y, rect.y + rect.height};
791
792 for (int k = 0; k < 4; k++) {
793 pChartBSB->chartpix_to_latlong(x[k], y[k], &lat, &lon);
794 ll[2 * k + 0] = lon, ll[2 * k + 1] = lat;
795 }
796
797 // resolve idl
798 float lonmin = ll[0], lonmax = ll[0];
799 float latmin = ll[1], latmax = ll[1];
800 for (int i = 2; i < 8; i += 2) {
801 lonmin = wxMin(lonmin, ll[i]), lonmax = wxMax(lonmax, ll[i]);
802 latmin = wxMin(latmin, ll[i + 1]), latmax = wxMax(latmax, ll[i + 1]);
803 }
804
805 if (fabsf(lonmin - lonmax) > 180) {
806 lonmin = 540, lonmax = 0;
807 for (int i = 0; i < 8; i += 2) {
808 float lon = ll[i] < 0 ? ll[i] + 360 : ll[i];
809 lonmin = wxMin(lonmin, lon), lonmax = wxMax(lonmax, lon);
810 }
811 }
812
813 tile->box.Set(latmin, lonmin, latmax, lonmax);
814
815 double xs = rect.width / xsplits;
816 double ys = rect.height / ysplits;
817 double x1 = rect.x, u1 = 0;
818
819 int maxncoords = 4 * xsplits * ysplits;
820 tile->m_coords = new float[2 * maxncoords];
821 tile->m_texcoords = new float[2 * maxncoords];
822
823 tile->m_ncoords = 0;
824
825 int end = 0; // should be 1<<base_level but we have no way to know now
826
827 for (int x = 0; x < xsplits; x++) {
828 double x2 = wxMin(x1 + xs, m_size_X - end);
829 double u2 = (x2 - rect.x) / rect.width;
830
831 double y1 = rect.y, v1 = 0;
832 for (int y = 0; y < ysplits; y++) {
833 double y2 = wxMin(y1 + ys, m_size_Y - end);
834 double v2 = (y2 - rect.y) / rect.height;
835
836 // todo avoid extra calls per loop and also caching from above
837 double xc[4] = {x1, x1, x2, x2}, yc[4] = {y2, y1, y1, y2};
838 double lat[4], lon[4];
839 for (int k = 0; k < 4; k++) {
840 pChartBSB->chartpix_to_latlong(xc[k], yc[k], lat + k, lon + k);
841 }
842
843 double u[4] = {u1, u1, u2, u2}, v[4] = {v2, v1, v1, v2};
844 for (int j = 0; j < 4; j++) {
845 int idx = 2 * tile->m_ncoords;
846 tile->m_texcoords[idx + 0] = u[j];
847 tile->m_texcoords[idx + 1] = v[j];
848
849 if (use_norm_vp) {
850 wxPoint2DDouble p = nvp.GetDoublePixFromLL(lat[j], lon[j]);
851 tile->m_coords[idx + 0] = p.m_x;
852 tile->m_coords[idx + 1] = p.m_y;
853 } else {
854 tile->m_coords[idx + 0] = lat[j];
855 tile->m_coords[idx + 1] = lon[j];
856 }
857 tile->m_ncoords++;
858 }
859
860 if (y1 + ys > m_size_Y - end) break;
861
862 v1 = v2;
863 y1 = y2;
864 }
865 if (x1 + xs > m_size_X - end) break;
866
867 u1 = u2;
868 x1 = x2;
869 }
870 rect.x += rect.width;
871 }
872 rect.y += rect.height;
873 }
874}
875
876bool glTexFactory::UpdateCacheLevel(const wxRect &rect, int level,
877 ColorScheme color_scheme,
878 unsigned char *data, int size) {
879 if (!g_GLOptions.m_bTextureCompressionCaching) return false;
880
881 if (!data) return false;
882
883 // Search for the requested texture
884 // Search the catalog for this particular texture
886 GetCacheEntryValue(level, rect.x, rect.y, color_scheme);
887
888 // This texture is already done
889 if (v != 0) return false;
890
891 return UpdateCachePrecomp(data, size, rect, level, color_scheme);
892}
893
894bool glTexFactory::UpdateCacheAllLevels(const wxRect &rect,
895 ColorScheme color_scheme,
896 unsigned char **compcomp_array,
897 int *compcomp_size) {
898 if (!g_GLOptions.m_bTextureCompressionCaching) return false;
899
900 bool work = false;
901
902 for (int level = 0; level < g_mipmap_max_level + 1; level++)
903 work |= UpdateCacheLevel(rect, level, color_scheme, compcomp_array[level],
904 compcomp_size[level]);
905 if (work) {
906 WriteCatalogAndHeader();
907 }
908
909 return work;
910}
911
912int glTexFactory::GetTextureLevel(glTextureDescriptor *ptd, const wxRect &rect,
913 int level, ColorScheme color_scheme) {
914 // Already available in the texture descriptor?
915 if (g_GLOptions.m_bTextureCompression) {
916 if (ptd->comp_array[level]) return COMPRESSED_BUFFER_OK;
917 if (ptd->compcomp_array[level]) {
918 // If we have the compcomp bits in ram decompress them
919 int size = TextureTileSize(level, true);
920 unsigned char *cb = (unsigned char *)malloc(size);
921 LZ4_decompress_fast((char *)ptd->compcomp_array[level], (char *)cb, size);
922 ptd->comp_array[level] = cb;
923 return COMPRESSED_BUFFER_OK;
924 } else if (g_GLOptions.m_bTextureCompressionCaching) {
925 // If cacheing compressed textures, look in the cache
926 // Search for the requested texture
927 // Search the catalog for this particular texture
929 GetCacheEntryValue(level, rect.x, rect.y, color_scheme);
930
931 // Requested texture level is found in the cache
932 // so go load it
933 if (p != 0) {
934 int size = TextureTileSize(level, true);
935
936 if (m_fs->IsOpened()) {
937 m_fs->Seek(p->texture_offset);
938 ptd->comp_array[level] = (unsigned char *)malloc(size);
939 int max_compressed_size = LZ4_COMPRESSBOUND(g_tile_size);
940 char *compressed_data = (char *)malloc(p->compressed_size);
941 m_fs->Read(compressed_data, p->compressed_size);
942 LZ4_decompress_fast(compressed_data, (char *)ptd->comp_array[level],
943 size);
944 free(compressed_data);
945 }
946
947 return COMPRESSED_BUFFER_OK;
948 }
949 }
950 }
951
952 // Requested Texture level is not in cache, and not already built
953 // So go build it
954 if (!ptd->map_array[level]) GetFullMap(ptd, rect, m_ChartPath, level);
955
956 return MAP_BUFFER_OK;
957}
958
959// return not used
960// false? never
961// true
962bool glTexFactory::LoadHeader(void) {
963 if (m_hdrOK) return true;
964
965 bool need_new = false;
966
967 if (wxFileName::FileExists(m_CompressedCacheFilePath)) {
968 m_fs = new wxFFile(m_CompressedCacheFilePath, _T("rb+"));
969 if (m_fs->IsOpened()) {
971
972 // Header is located at the end of the file
973 wxFileOffset hdr_offset = m_fs->Length() - sizeof(hdr);
974 hdr_offset = m_fs->Seek(hdr_offset);
975
976 if (sizeof(hdr) == m_fs->Read(&hdr, sizeof(hdr))) {
977 if (hdr.magic != COMPRESSED_CACHE_MAGIC ||
978 hdr.chartdate != m_chart_date_binary ||
979 hdr.chartfile_date != m_chartfile_date_binary ||
980 hdr.chartfile_size != m_chartfile_size ||
981 hdr.format != g_raster_format) {
982 // Bad header signature
983 delete m_fs;
984 need_new = true;
985 } else { // good header
986 n_catalog_entries = hdr.m_nentries;
987 m_catalog_offset = hdr.catalog_offset;
988 }
989 } else { // file exists, and is empty
990 n_catalog_entries = 0;
991 m_catalog_offset = 0;
992 WriteCatalogAndHeader();
993 }
994 } // is open
995
996 else { // some problem opening file, probably permissions on Win7
997 delete m_fs;
998 need_new = true;
999 wxRemoveFile(m_CompressedCacheFilePath);
1000 }
1001
1002 } // exists
1003
1004 else { // File does not exist
1005 wxFileName fn(m_CompressedCacheFilePath);
1006 if (!fn.DirExists()) fn.Mkdir();
1007 need_new = true;
1008 }
1009
1010 if (need_new) {
1011 // Create new file, with empty catalog, and correct header
1012 m_fs = new wxFFile(m_CompressedCacheFilePath, _T("wb"));
1013 n_catalog_entries = 0;
1014 m_catalog_offset = 0;
1015 WriteCatalogAndHeader();
1016 delete m_fs;
1017
1018 m_fs = new wxFFile(m_CompressedCacheFilePath, _T("rb+"));
1019 }
1020 m_hdrOK = true;
1021 return true;
1022}
1023
1024bool glTexFactory::AddCacheEntryValue(const CatalogEntry &p) {
1025 if ((int)p.k.tcolorscheme < 0 || p.k.tcolorscheme >= N_COLOR_SCHEMES)
1026 return false;
1027
1028 if (p.k.mip_level < 0 || p.k.mip_level >= MAX_TEX_LEVEL) return false;
1029
1030 int array_index = ArrayIndex(p.k.x, p.k.y);
1031 if (array_index < 0 || array_index >= m_ntex) return false;
1032
1033 if (m_cache[p.k.tcolorscheme][p.k.mip_level] == 0)
1034 m_cache[p.k.tcolorscheme][p.k.mip_level] =
1035 (CatalogEntryValue *)calloc(m_ntex, sizeof(CatalogEntryValue));
1036
1037 CatalogEntryValue *v = m_cache[p.k.tcolorscheme][p.k.mip_level];
1038 CatalogEntryValue *r = &v[array_index];
1039 *r = p.v;
1040 return true;
1041}
1042
1043bool glTexFactory::LoadCatalog(void) {
1044 m_newCatalog = false;
1045 if (m_catalogOK) return true;
1046
1047 if (!LoadHeader()) return false;
1048
1049 if (n_catalog_entries == 0) {
1050 // new empty header
1051 m_catalogOK = true;
1052 m_newCatalog = true;
1053 return true;
1054 }
1055
1056 m_fs->Seek(m_catalog_offset);
1057
1058 CatalogEntry ps;
1059 int buf_size = ps.GetSerialSize();
1060 unsigned char *buf = (unsigned char *)malloc(buf_size);
1061
1062 CatalogEntry p;
1063 bool bad = false;
1064 for (int i = 0; i < n_catalog_entries; i++) {
1065 m_fs->Read(buf, buf_size);
1066 p.DeSerialize(buf);
1067 if (!AddCacheEntryValue(p)) bad = true;
1068 }
1069
1070 free(buf);
1071 if (bad && !m_catalogCorrupted) {
1072 wxLogMessage(_T("Bad cache catalog %s %s"), m_ChartPath.c_str(),
1073 m_CompressedCacheFilePath.c_str());
1074 m_catalogCorrupted = true;
1075 }
1076 m_catalogOK = true;
1077 return true;
1078}
1079
1080bool glTexFactory::WriteCatalogAndHeader() {
1081 if (m_fs && m_fs->IsOpened()) {
1082 m_fs->Seek(m_catalog_offset);
1083
1084 CatalogEntry ps;
1085 int buf_size = ps.GetSerialSize();
1086 unsigned char buf[CATALOG_ENTRY_SERIAL_SIZE]; // MSVC requires constant
1087 // stack array size...
1088 int new_n_catalog_entries = 0;
1089 CatalogEntry p;
1090 wxRect rect;
1091 for (int i = 0; i < N_COLOR_SCHEMES; i++) {
1092 p.k.tcolorscheme = (ColorScheme)i;
1093 for (int j = 0; j < MAX_TEX_LEVEL; j++) {
1094 CatalogEntryValue *v = m_cache[i][j];
1095 if (!v) continue;
1096 p.k.mip_level = j;
1097 for (int k = 0; k < m_ntex; k++) {
1098 ArrayXY(&rect, k);
1099 p.k.y = rect.y;
1100 p.k.x = rect.x;
1101 CatalogEntryValue *r = &v[k];
1102 if (r->compressed_size == 0) continue;
1103 p.v = *r;
1104 new_n_catalog_entries++;
1105 p.Serialize(buf);
1106 m_fs->Write(buf, buf_size);
1107 }
1108 }
1109 }
1110
1111 n_catalog_entries = new_n_catalog_entries;
1112 // Write header at file end
1114 hdr.magic = COMPRESSED_CACHE_MAGIC;
1115 hdr.format = g_raster_format;
1116 hdr.m_nentries = n_catalog_entries;
1117 hdr.catalog_offset = m_catalog_offset;
1118 hdr.chartdate = m_chart_date_binary;
1119 hdr.chartfile_date = m_chartfile_date_binary;
1120 hdr.chartfile_size = m_chartfile_size;
1121
1122 m_fs->Write(&hdr, sizeof(hdr));
1123 m_fs->Flush();
1124
1125 return true;
1126 } else
1127 return false;
1128}
1129
1130bool glTexFactory::UpdateCachePrecomp(unsigned char *data, int data_size,
1131 const wxRect &rect, int level,
1132 ColorScheme color_scheme,
1133 bool write_catalog) {
1134 if (level < 0 || level >= MAX_TEX_LEVEL) return false; // XXX BUG
1135
1136 // Search the catalog for this particular texture
1137 if (GetCacheEntryValue(level, rect.x, rect.y, color_scheme) != 0)
1138 return false;
1139
1140 // Make sure the file exists
1141 wxASSERT(m_fs != 0);
1142
1143 if (!m_fs->IsOpened()) return false;
1144
1145 // Create a new catalog entry
1146 CatalogEntry p(level, rect.x, rect.y, color_scheme);
1147
1148 // Write the compressed data to disk
1149 p.v.texture_offset = m_catalog_offset;
1150
1151 p.v.compressed_size = data_size;
1152 AddCacheEntryValue(p);
1153 n_catalog_entries++;
1154
1155 // We write the new data at the current catalog offset, overwriting the
1156 // old catalog
1157 m_fs->Seek(m_catalog_offset);
1158 m_fs->Write(data, data_size);
1159
1160 // Write the catalog and Header (which follows the catalog at the end of
1161 // the file
1162 m_catalog_offset += data_size;
1163 if (write_catalog) WriteCatalogAndHeader();
1164
1165 return true;
1166}