OpenCPN Partial API docs
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__
61 typedef void (*PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers);
62 typedef void (*PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
63 typedef void (*PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers);
64 typedef void (*PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname,
65  GLint *params);
66 typedef void (*PFNGLDELETERENDERBUFFERSEXTPROC)(GLsizei n,
67  const GLuint *renderbuffers);
68 typedef void (*PFNGLDELETEFRAMEBUFFERSEXTPROC)(GLsizei n,
69  const GLuint *framebuffers);
70 typedef void (*PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level,
71  GLint xoffset, GLsizei width,
72  GLenum format,
73  GLsizei imageSize,
74  const GLvoid *data);
75 typedef void (*PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level,
76  GLvoid *img);
77 typedef GLenum (*PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)(GLenum target);
78 typedef void (*PFNGLBINDRENDERBUFFEREXTPROC)(GLenum target,
79  GLuint renderbuffer);
80 typedef void (*PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size,
81  const GLvoid *data, GLenum usage);
82 typedef void (*PFNGLGENFRAMEBUFFERSEXTPROC)(GLsizei n, GLuint *framebuffers);
83 typedef void (*PFNGLGENRENDERBUFFERSEXTPROC)(GLsizei n, GLuint *renderbuffers);
84 typedef void (*PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)(GLenum target,
85  GLenum attachment,
86  GLenum textarget,
87  GLuint texture, GLint level);
88 typedef void (*PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level,
89  GLenum internalformat,
90  GLsizei width, GLsizei height,
91  GLint border, GLsizei imageSize,
92  const GLvoid *data);
93 typedef void (*PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)(GLenum target,
94  GLenum attachment,
95  GLenum renderbuffertarget,
96  GLuint renderbuffer);
97 typedef void (*PFNGLRENDERBUFFERSTORAGEEXTPROC)(GLenum target,
98  GLenum internalformat,
99  GLsizei width, GLsizei height);
100 typedef void (*PFNGLBINDFRAMEBUFFEREXTPROC)(GLenum target, GLuint framebuffer);
101 #endif
102 
103 extern long g_tex_mem_used;
104 extern int g_mipmap_max_level;
105 extern GLuint g_raster_format;
106 extern int g_memCacheLimit;
107 
108 extern ColorScheme global_color_scheme;
109 
110 extern ChartDB *ChartData;
111 extern ocpnGLOptions g_GLOptions;
112 
113 extern int g_tile_size;
114 
115 extern bool GetMemoryStatus(int *mem_total, int *mem_used);
116 
117 extern wxString CompressedCachePath(wxString path);
118 extern glTextureManager *g_glTextureManager;
119 
120 // CatalogEntry implementation
121 CatalogEntry::CatalogEntry() {}
122 
123 CatalogEntry::~CatalogEntry() {}
124 
125 CatalogEntry::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 
132 int CatalogEntry::GetSerialSize() { return CATALOG_ENTRY_SERIAL_SIZE; }
133 
134 void 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 
145 void 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
157 enum TextureDataType { COMPRESSED_BUFFER_OK, MAP_BUFFER_OK };
158 
159 glTexFactory::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 
207 glTexFactory::~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 
230 glTextureDescriptor *glTexFactory::GetpTD(wxRect &rect) {
231  int array_index = ArrayIndex(rect.x, rect.y);
232  return m_td_array[array_index];
233 }
234 
235 bool 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 
320 void 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 
332 void 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 
342 void 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 
361 void 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 
383 void 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 
391 void 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 
401 bool glTexFactory::BackgroundCompressionAsJob() const {
402  return g_glTextureManager->AsJob(m_ChartPath);
403 }
404 
405 void glTexFactory::PurgeBackgroundCompressionPool() {
406  // Purge the "todo" list, and allow any running jobs to complete normally
407  g_glTextureManager->PurgeJobList(m_ChartPath);
408 }
409 
410 void 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 
422 void 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 
427 CatalogEntryValue *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 
446 bool 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 
460 glTextureDescriptor *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 
474 static 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 
497 bool 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  BasePlatform::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) AbstractPlatform::HideBusySpinner();
635 
636  return true;
637 }
638 
639 bool 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 
703 void 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 
876 bool 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
885  CatalogEntryValue *v =
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 
894 bool 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 
912 int 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
928  CatalogEntryValue *p =
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
962 bool 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 
1024 bool 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 
1043 bool 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 
1080 bool 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 
1130 bool 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 }