OpenCPN Partial API docs
glTextureManager.cpp
1 /******************************************************************************
2  *
3  * Project: OpenCPN
4  * Authors: David Register
5  * Sean D'Epagnier
6  *
7  ***************************************************************************
8  * Copyright (C) 2016 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/progdlg.h>
29 #include <wx/wx.h>
30 #include <wx/thread.h>
31 
32 #if defined(__OCPN__ANDROID__)
33 #include <GLES2/gl2.h>
34 #elif defined(__WXQT__) || defined(__WXGTK__)
35 #include <GL/glew.h>
36 #endif
37 
38 #include "dychart.h"
39 #include "viewport.h"
40 #include "glTexCache.h"
41 #include "glTextureDescriptor.h"
42 
43 #include "chcanv.h"
44 #include "glChartCanvas.h"
45 #include "Quilt.h"
46 #include "chartbase.h"
47 #include "chartimg.h"
48 #include "chartdb.h"
49 #include "OCPNPlatform.h"
50 #include "FontMgr.h"
51 #include "mipmap/mipmap.h"
52 #include "gui_lib.h"
53 #include "ocpn_frame.h"
54 #include "model/own_ship.h"
55 
56 #ifndef GL_ETC1_RGB8_OES
57 #define GL_ETC1_RGB8_OES 0x8D64
58 #endif
59 
60 #include "squish.h"
61 #include "lz4.h"
62 #include "lz4hc.h"
63 
64 #include <wx/listimpl.cpp>
65 WX_DEFINE_LIST(JobList);
66 WX_DEFINE_LIST(ProgressInfoList);
67 
68 WX_DEFINE_ARRAY_PTR(ChartCanvas *, arrayofCanvasPtr);
69 
70 extern int g_mipmap_max_level;
71 extern GLuint g_raster_format;
72 extern int g_memCacheLimit;
73 extern ChartDB *ChartData;
74 extern ocpnGLOptions g_GLOptions;
75 extern long g_tex_mem_used;
76 extern int g_tile_size;
77 extern int g_uncompressed_tile_size;
78 extern int g_nCPUCount;
79 
80 extern bool b_inCompressAllCharts;
81 extern MyFrame *gFrame;
82 extern arrayofCanvasPtr g_canvasArray;
83 
84 extern OCPNPlatform *g_Platform;
85 extern ColorScheme global_color_scheme;
86 
87 extern bool GetMemoryStatus(int *mem_total, int *mem_used);
88 
89 bool bthread_debug;
90 bool g_throttle_squish;
91 
92 glTextureManager *g_glTextureManager;
93 
94 #include "ssl/sha1.h"
95 
96 wxString CompressedCachePath(wxString path) {
97 #if defined(__WXMSW__)
98  int colon = path.find(':', 0);
99  if (colon != wxNOT_FOUND) path.Remove(colon, 1);
100 #endif
101 
102  /* replace path separators with ! */
103  wxChar separator = wxFileName::GetPathSeparator();
104  for (unsigned int pos = 0; pos < path.size(); pos = path.find(separator, pos))
105  path.replace(pos, 1, _T("!"));
106 
107  // Obfuscate the compressed chart file name, to (slightly) protect some
108  // encrypted raster chart data.
109  wxCharBuffer buf = path.ToUTF8();
110  unsigned char sha1_out[20];
111  sha1((unsigned char *)buf.data(), strlen(buf.data()), sha1_out);
112 
113  wxString sha1;
114  for (unsigned int i = 0; i < 20; i++) {
115  wxString s;
116  s.Printf(_T("%02X"), sha1_out[i]);
117  sha1 += s;
118  }
119 
120  return g_Platform->GetPrivateDataDir() + separator +
121  _T("raster_texture_cache") + separator + sha1;
122 }
123 
124 int g_mipmap_max_level = 4;
125 
126 #if 0
127 OCPN_CompressProgressEvent::OCPN_CompressProgressEvent(wxEventType commandType, int id)
128 :wxEvent(id, commandType)
129 {
130 }
131 
132 OCPN_CompressProgressEvent::~OCPN_CompressProgressEvent()
133 {
134 }
135 
136 wxEvent* OCPN_CompressProgressEvent::Clone() const
137 {
138  OCPN_CompressProgressEvent *newevent=new OCPN_CompressProgressEvent(*this);
139  newevent->m_string=this->m_string;
140  newevent->count=this->count;
141  newevent->thread=this->thread;
142  return newevent;
143 }
144 #endif
145 
146 static double chart_dist(int index) {
147  double d;
148  float clon;
149  float clat;
150  const ChartTableEntry &cte = ChartData->GetChartTableEntry(index);
151  // if the chart contains ownship position set the distance to 0
152  if (cte.GetBBox().Contains(gLat, gLon))
153  d = 0.;
154  else {
155  // find the nearest edge
156  double t;
157  clon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
158  d = DistGreatCircle(cte.GetLatMax(), clon, gLat, gLon);
159  t = DistGreatCircle(cte.GetLatMin(), clon, gLat, gLon);
160  if (t < d) d = t;
161 
162  clat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
163  t = DistGreatCircle(clat, cte.GetLonMin(), gLat, gLon);
164  if (t < d) d = t;
165  t = DistGreatCircle(clat, cte.GetLonMax(), gLat, gLon);
166  if (t < d) d = t;
167  }
168  return d;
169 }
170 
171 WX_DEFINE_SORTED_ARRAY_INT(int, MySortedArrayInt);
172 int CompareInts(int n1, int n2) {
173  double d1 = chart_dist(n1);
174  double d2 = chart_dist(n2);
175  return (int)(d1 - d2);
176 }
177 
178 static MySortedArrayInt idx_sorted_by_distance(CompareInts);
179 
181 public:
182  wxString chart_path;
183  double distance;
184 };
185 
186 #include <wx/arrimpl.cpp>
187 
188 WX_DECLARE_OBJARRAY(compress_target, ArrayOfCompressTargets);
189 // WX_DEFINE_OBJARRAY(ArrayOfCompressTargets);
190 
191 JobTicket::JobTicket() {
192  for (int i = 0; i < 10; i++) {
193  compcomp_size_array[i] = 0;
194  comp_bits_array[i] = NULL;
195  compcomp_bits_array[i] = NULL;
196  }
197 }
198 
199 #if 0
200 /* reduce pixel values to 5/6/5, because this is the format they are stored
201  * when compressed anyway, and this way the compression algorithm will use
202  * the exact same color in adjacent 4x4 tiles and the result is nicer for our purpose.
203  * the lz4 compressed texture is smaller as well. */
204 static
205 void FlattenColorsForCompression(unsigned char *data, int dim, bool swap_colors=true)
206 {
207 #ifdef __WXMSW__ /* undo BGR flip from ocpn_pixel (if ocpnUSE_ocpnBitmap is \
208  defined) */
209  if(swap_colors)
210  for(int i = 0; i<dim*dim; i++) {
211  int off = 3*i;
212  unsigned char t = data[off + 0];
213  data[off + 0] = data[off + 2] & 0xfc;
214  data[off + 1] &= 0xf8;
215  data[off + 2] = t & 0xfc;
216  }
217  else
218 #endif
219  for(int i = 0; i<dim*dim; i++) {
220  int off = 3*i;
221  data[off + 0] &= 0xfc;
222  data[off + 1] &= 0xf8;
223  data[off + 2] &= 0xfc;
224  }
225 }
226 #endif
227 
228 /* return malloced data which is the etc compressed texture of the source */
229 static void CompressDataETC(const unsigned char *data, int dim, int size,
230  unsigned char *tex_data, volatile bool &b_abort) {
231  wxASSERT(dim * dim == 2 * size || (dim < 4 && size == 8)); // must be 4bpp
232  uint64_t *tex_data64 = (uint64_t *)tex_data;
233 
234  int mbrow = wxMin(4, dim), mbcol = wxMin(4, dim);
235  uint8_t block[48] = {};
236  for (int row = 0; row < dim; row += 4) {
237  for (int col = 0; col < dim; col += 4) {
238  for (int brow = 0; brow < mbrow; brow++)
239  for (int bcol = 0; bcol < mbcol; bcol++)
240  memcpy(block + (bcol * 4 + brow) * 3,
241  data + ((row + brow) * dim + col + bcol) * 3, 3);
242 
243  extern uint64_t ProcessRGB(const uint8_t *src);
244  *tex_data64++ = ProcessRGB(block);
245  }
246  if (b_abort) break;
247  }
248 }
249 
250 static bool CompressUsingGPU(const unsigned char *data, int dim, int size,
251  unsigned char *tex_data, int level, bool inplace) {
252 #ifndef USE_ANDROID_GLES2
253 
254  GLuint comp_tex;
255  if (!inplace) {
256  glGenTextures(1, &comp_tex);
257  glBindTexture(GL_TEXTURE_2D, comp_tex);
258  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
259  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
260  level = 0;
261  }
262 
263  glTexImage2D(GL_TEXTURE_2D, level, g_raster_format, dim, dim, 0, GL_RGB,
264  GL_UNSIGNED_BYTE, data);
265 
266  GLint compressed;
267  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB,
268  &compressed);
269  /* if the compression has been successful */
270  if (compressed == GL_TRUE) {
271  // If our compressed size is reasonable, save it.
272  GLint compressedSize;
273  glGetTexLevelParameteriv(GL_TEXTURE_2D, level,
274  GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedSize);
275 
276  if (compressedSize != size) return false;
277 
278  // Read back the compressed texture.
279  glGetCompressedTexImage(GL_TEXTURE_2D, level, tex_data);
280  }
281 
282  if (!inplace) glDeleteTextures(1, &comp_tex);
283 
284  return true;
285 #else
286  return false;
287 #endif
288 }
289 
290 static void GetLevel0Map(glTextureDescriptor *ptd, const wxRect &rect,
291  wxString &chart_path) {
292  // Load level 0 uncompressed data
293  wxRect ncrect(rect);
294  ptd->map_array[0] = 0;
295 
296  ChartBase *pChart = ChartData->OpenChartFromDB(chart_path, FULL_INIT);
297  if (!pChart) {
298  ptd->map_array[0] =
299  (unsigned char *)calloc(ncrect.width * ncrect.height * 4, 1);
300  return;
301  }
302 
303  // Prime the pump with the "zero" level bits, ie. 1x native chart bits
304  ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pChart);
305 
306  if (pBSBChart) {
307  unsigned char *t_buf =
308  (unsigned char *)malloc(ncrect.width * ncrect.height * 4);
309  pBSBChart->GetChartBits(ncrect, t_buf, 1);
310 
311  // and cache them here
312  ptd->map_array[0] = t_buf;
313  } else {
314  ptd->map_array[0] =
315  (unsigned char *)calloc(ncrect.width * ncrect.height * 4, 1);
316  return;
317  }
318 }
319 
320 void GetFullMap(glTextureDescriptor *ptd, const wxRect &rect,
321  wxString chart_path, int level) {
322  // Confirm that the uncompressed bits are all available, get them if not
323  // there yet
324  if (ptd->map_array[level]) return;
325 
326  // find next lower level with map_array
327  int first_level;
328  for (first_level = level; first_level; first_level--)
329  if (ptd->map_array[first_level - 1]) break;
330 
331  // Get level 0 bits from chart?
332  if (!first_level) {
333  GetLevel0Map(ptd, rect, chart_path);
334  first_level = 1;
335  }
336 
337  int dim = g_GLOptions.m_iTextureDimension;
338  for (int i = 0; i <= level; i++) {
339  if (i >= first_level) {
340  ptd->map_array[i] = (unsigned char *)malloc(dim * dim * 3);
341  MipMap_24(2 * dim, 2 * dim, ptd->map_array[i - 1], ptd->map_array[i]);
342  }
343  dim /= 2;
344  }
345 }
346 
347 int TextureDim(int level) {
348  int dim = g_GLOptions.m_iTextureDimension;
349  for (int i = 0; i < level; i++) dim /= 2;
350  return dim;
351 }
352 
353 int TextureTileSize(int level, bool compressed) {
354  if (level == g_mipmap_max_level + 1) return 0;
355 
356  int size;
357  if (compressed) {
358  size = g_tile_size;
359  for (int i = 0; i < level; i++) {
360  size /= 4;
361  if (size < 8) size = 8;
362  }
363  } else {
364  size = g_uncompressed_tile_size;
365  for (int i = 0; i < level; i++) size /= 4;
366  }
367 
368  return size;
369 }
370 
371 bool JobTicket::DoJob() {
372  if (!m_rect.IsEmpty()) return DoJob(m_rect);
373 
374  // otherwise this ticket covers all the rects in the chart
375  ChartBase *pchart = ChartData->OpenChartFromDB(m_ChartPath, FULL_INIT);
376  if (!pchart) return false;
377 
378  ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pchart);
379  if (!pBSBChart) return false;
380 
381  int size_X = pBSBChart->GetSize_X();
382  int size_Y = pBSBChart->GetSize_Y();
383 
384  int dim = g_GLOptions.m_iTextureDimension;
385 
386  int nx_tex = ceil((float)size_X / dim);
387  int ny_tex = ceil((float)size_Y / dim);
388 
389  wxRect rect;
390  rect.y = 0;
391  rect.width = dim;
392  rect.height = dim;
393  for (int y = 0; y < ny_tex; y++) {
394  if (pthread && pthread->m_pMessageTarget) {
395  OCPN_CompressionThreadEvent Nevent(wxEVT_OCPN_COMPRESSIONTHREAD, 0);
396  Nevent.nstat = y;
397  Nevent.nstat_max = ny_tex;
398  Nevent.type = 1;
399  Nevent.SetTicket(this);
400  pthread->m_pMessageTarget->AddPendingEvent(Nevent);
401  }
402 
403  rect.x = 0;
404  for (int x = 0; x < nx_tex; x++) {
405  if (!DoJob(rect)) return false;
406 
407  pFact->UpdateCacheAllLevels(rect, global_color_scheme,
408  compcomp_bits_array, compcomp_size_array);
409 
410  for (int i = 0; i < g_mipmap_max_level + 1; i++) {
411  free(comp_bits_array[i]), comp_bits_array[i] = 0;
412  free(compcomp_bits_array[i]), compcomp_bits_array[i] = 0;
413  }
414 
415  rect.x += rect.width;
416  }
417  rect.y += rect.height;
418  }
419 
420  return true;
421 }
422 
423 #if 0 // defined( __UNIX__ ) && !defined(__WXOSX__) // high resolution
424  // stopwatch for pro
425 class OCPNStopWatch
426 {
427 public:
428  OCPNStopWatch() { Start(); }
429  void Start() { clock_gettime(CLOCK_REALTIME, &tp); }
430 
431  double Time() {
432  timespec tp_end;
433  clock_gettime(CLOCK_REALTIME, &tp_end);
434  return (tp_end.tv_sec - tp.tv_sec) * 1.e3 + (tp_end.tv_nsec - tp.tv_nsec) / 1.e6;
435  }
436 
437 private:
438  timespec tp;
439 };
440 #else
441 class OCPNStopWatch : public wxStopWatch {};
442 #endif
443 
444 static void throttle_func(void *data) {
445  if (!wxThread::IsMain()) {
446  OCPNStopWatch *sww = (OCPNStopWatch *)data;
447  if (sww->Time() > 1) {
448  sww->Start();
449  wxThread::Sleep(2);
450  }
451  }
452 }
453 
454 static wxMutex s_mutexProtectingChartBitRead;
455 
456 bool JobTicket::DoJob(const wxRect &rect) {
457  unsigned char *bit_array[10];
458  for (int i = 0; i < 10; i++) bit_array[i] = 0;
459 
460  wxRect ncrect(rect);
461 
462  bit_array[0] = level0_bits;
463  level0_bits = NULL;
464 
465  if (!bit_array[0]) {
466  // Grab a copy of the level0 chart bits
467  // we could alternately subsample grabbing leveln chart bits
468  // directly here to speed things up...
469  ChartBase *pchart;
470  int index;
471 
472  if (ChartData) {
473  wxMutexLocker lock(s_mutexProtectingChartBitRead);
474 
475  index = ChartData->FinddbIndex(m_ChartPath);
476  pchart = ChartData->OpenChartFromDBAndLock(index, FULL_INIT);
477 
478  if (pchart && ChartData->IsChartLocked(index)) {
479  ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pchart);
480  if (pBSBChart) {
481  bit_array[0] =
482  (unsigned char *)malloc(ncrect.width * ncrect.height * 4);
483  pBSBChart->GetChartBits(ncrect, bit_array[0], 1);
484  }
485  ChartData->UnLockCacheChart(index);
486  } else
487  bit_array[0] = NULL;
488  }
489  }
490 
491  // OK, got the bits?
492  int dim;
493  if (!bit_array[0]) return false;
494 
495  // Fill in the rest of the private uncompressed array
496  dim = g_GLOptions.m_iTextureDimension;
497  dim /= 2;
498  for (int i = 1; i < g_mipmap_max_level + 1; i++) {
499  size_t nmalloc = wxMax(dim * dim * 3, 4 * 4 * 3);
500  bit_array[i] = (unsigned char *)malloc(nmalloc);
501  MipMap_24(2 * dim, 2 * dim, bit_array[i - 1], bit_array[i]);
502  dim /= 2;
503  }
504 
505  int texture_level = 0;
506  for (int level = level_min_request; level < g_mipmap_max_level + 1; level++) {
507  int dim = TextureDim(level);
508  int size = TextureTileSize(level, true);
509  unsigned char *tex_data = (unsigned char *)malloc(size);
510  if (g_raster_format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) {
511  // color range fit is worse quality but twice as fast
512  int flags = squish::kDxt1 | squish::kColourRangeFit;
513 
514  if (g_GLOptions.m_bTextureCompressionCaching) {
515  /* use slower cluster fit since we are building the cache for
516  * better quality, this takes roughly 25% longer and uses about
517  * 10% more disk space (result doesn't compress as well with lz4) */
518  flags = squish::kDxt1 | squish::kColourClusterFit;
519  }
520 
521  OCPNStopWatch sww;
522  squish::CompressImageRGBpow2_Flatten_Throttle_Abort(
523  bit_array[level], dim, dim, tex_data, flags, true,
524  b_throttle ? throttle_func : 0, &sww, b_abort);
525 
526  } else if (g_raster_format == GL_ETC1_RGB8_OES)
527  CompressDataETC(bit_array[level], dim, size, tex_data, b_abort);
528  else if (g_raster_format == GL_COMPRESSED_RGB_FXT1_3DFX) {
529  if (!CompressUsingGPU(bit_array[level], dim, size, tex_data,
530  texture_level, binplace)) {
531  b_abort = true;
532  break;
533  }
534 
535  if (binplace) g_tex_mem_used += size;
536 
537  texture_level++;
538  }
539  comp_bits_array[level] = tex_data;
540 
541  if (b_abort) {
542  for (int i = 0; i < g_mipmap_max_level + 1; i++) {
543  free(bit_array[i]);
544  bit_array[i] = 0;
545  }
546  return false;
547  }
548  }
549 
550  // All done with the uncompressed data in the thread
551  for (int i = 0; i < g_mipmap_max_level + 1; i++) {
552  free(bit_array[i]);
553  bit_array[i] = 0;
554  }
555 
556  if (b_throttle) wxThread::Sleep(1);
557 
558  if (b_abort) return false;
559 
560  if (bpost_zip_compress) {
561  int max_compressed_size = LZ4_COMPRESSBOUND(g_tile_size);
562  for (int level = level_min_request; level < g_mipmap_max_level + 1;
563  level++) {
564  if (b_abort) return false;
565 
566  unsigned char *compressed_data =
567  (unsigned char *)malloc(max_compressed_size);
568  int csize = TextureTileSize(level, true);
569 
570  char *src = (char *)comp_bits_array[level];
571  int compressed_size =
572  LZ4_compressHC2(src, (char *)compressed_data, csize, 4);
573  // shrink buffer to actual size.
574  // This will greatly reduce ram usage, ratio usually 10:1
575  // there might be a more efficient way than realloc...
576  compressed_data =
577  (unsigned char *)realloc(compressed_data, compressed_size);
578  compcomp_bits_array[level] = compressed_data;
579  compcomp_size_array[level] = compressed_size;
580  }
581  }
582 
583  return true;
584 }
585 
586 // On Windows, we will use a translator to convert SEH exceptions (e.g. access
587 // violations),
588 // into c++ standard exception handling method.
589 // This class and helper function facilitate the conversion.
590 
591 // We only do this in the compression worker threads, as they are vulnerable
592 // due to possibly errant code in the chart database management class,
593 // especially on low memory systems where chart cahing is stressed heavily.
594 
595 #ifdef __WXMSW__
596 class SE_Exception {
597 private:
598  unsigned int nSE;
599 
600 public:
601  SE_Exception() {}
602  SE_Exception(unsigned int n) : nSE(n) {}
603  ~SE_Exception() {}
604  unsigned int getSeNumber() { return nSE; }
605 };
606 
607 void my_translate(unsigned int code, _EXCEPTION_POINTERS *ep) {
608  throw SE_Exception();
609 }
610 #endif
611 
612 OCPN_CompressionThreadEvent::OCPN_CompressionThreadEvent(
613  wxEventType commandType, int id)
614  : wxEvent(id, commandType) {
615  type = 0;
616 }
617 
618 OCPN_CompressionThreadEvent::~OCPN_CompressionThreadEvent() {}
619 
620 wxEvent *OCPN_CompressionThreadEvent::Clone() const {
621  OCPN_CompressionThreadEvent *newevent =
622  new OCPN_CompressionThreadEvent(*this);
623  newevent->m_ticket = this->m_ticket;
624  newevent->type = this->type;
625  newevent->nstat = this->nstat;
626  newevent->nstat_max = this->nstat_max;
627  /*
628  newevent->m_ticket = new JobTicket;
629 
630  newevent->m_ticket->pFact = this->m_ticket->pFact;
631  newevent->m_ticket->rect = this->m_ticket->rect;
632  newevent->m_ticket->level_min_request = this->m_ticket->level_min_request;
633  newevent->m_ticket->ident = this->m_ticket->ident;
634  newevent->m_ticket->b_throttle = this->m_ticket->b_throttle;
635  newevent->m_ticket->pthread = this->m_ticket->pthread;
636  newevent->m_ticket->level0_bits = this->m_ticket->level0_bits;
637  newevent->m_ticket->m_ChartPath = this->m_ticket->m_ChartPath;
638  newevent->m_ticket->b_abort = this->m_ticket->b_abort;
639  newevent->m_ticket->b_isaborted = this->m_ticket->b_isaborted;
640  newevent->m_ticket->bpost_zip_compress =
641  this->m_ticket->bpost_zip_compress; newevent->m_ticket->state =
642  this->m_ticket->state; newevent->m_ticket->tx = this->m_ticket->tx;
643  newevent->m_ticket->nx = this->m_ticket->nx;
644  newevent->m_ticket->ty = this->m_ticket->ty;
645  newevent->m_ticket->ny = this->m_ticket->ny;
646  for(int i = 0 ; i < 10 ; i++){
647  newevent->m_ticket->comp_bits_array[i] =
648  this->m_ticket->comp_bits_array[i];
649  newevent->m_ticket->compcomp_bits_array[i] =
650  this->m_ticket->compcomp_bits_array[i];
651  newevent->m_ticket->compcomp_size_array[i] =
652  this->m_ticket->compcomp_size_array[i];
653  }
654  */
655  return newevent;
656 }
657 
658 CompressionPoolThread::CompressionPoolThread(JobTicket *ticket,
659  wxEvtHandler *message_target) {
660  m_pMessageTarget = message_target;
661  m_ticket = ticket;
662 
663  Create();
664 }
665 
666 void *CompressionPoolThread::Entry() {
667 #ifdef __MSVC__
668  _set_se_translator(my_translate);
669 
670  // On Windows, if anything in this thread produces a SEH exception (like
671  // access violation) we handle the exception locally, and simply alow the
672  // thread to exit smoothly with no results. Upstream will notice that nothing
673  // got done, and maybe try again later.
674 
675  try
676 #endif
677  {
678  SetPriority(WXTHREAD_MIN_PRIORITY);
679 
680  if (!m_ticket->DoJob()) m_ticket->b_isaborted = true;
681 
682  if (m_pMessageTarget) {
683  OCPN_CompressionThreadEvent Nevent(wxEVT_OCPN_COMPRESSIONTHREAD, 0);
684  Nevent.SetTicket(m_ticket);
685  Nevent.type = 0;
686  m_pMessageTarget->QueueEvent(Nevent.Clone());
687  // from here m_ticket is undefined (if deleted in event handler)
688  }
689 
690  return 0;
691 
692  } // try
693 #ifdef __MSVC__
694  catch (SE_Exception e) {
695  if (m_pMessageTarget) {
696  OCPN_CompressionThreadEvent Nevent(wxEVT_OCPN_COMPRESSIONTHREAD, 0);
697  m_ticket->b_isaborted = true;
698  Nevent.SetTicket(m_ticket);
699  Nevent.type = 0;
700  m_pMessageTarget->QueueEvent(Nevent.Clone());
701  }
702 
703  return 0;
704  }
705 #endif
706 }
707 
708 // ProgressInfoItem Implementation
709 
710 // glTextureManager Implementation
711 glTextureManager::glTextureManager() {
712  // ideally we would use the cpu count -1, and only launch jobs
713  // when the idle load average is sufficient (greater than 1)
714  int nCPU = wxMax(1, wxThread::GetCPUCount());
715  if (g_nCPUCount > 0) nCPU = g_nCPUCount;
716 
717  if (nCPU < 1)
718  // obviously there's at least one CPU!
719  nCPU = 1;
720 
721  m_max_jobs = wxMax(nCPU, 1);
722  m_prevMemUsed = 0;
723 
724  if (bthread_debug) printf(" nCPU: %d m_max_jobs :%d\n", nCPU, m_max_jobs);
725 
726  m_progDialog = NULL;
727 
728  for (int i = 0; i < m_max_jobs; i++) progList.Append(new ProgressInfoItem);
729 
730  // Create/connect a dynamic event handler slot for messages from the worker
731  // threads
732  Connect(
733  wxEVT_OCPN_COMPRESSIONTHREAD,
734  (wxObjectEventFunction)(wxEventFunction)&glTextureManager::OnEvtThread);
735 
736  m_ticks = 0;
737  m_skip = false;
738  m_bcompact = false;
739  m_skipout = false;
740 
741  m_timer.Connect(wxEVT_TIMER, wxTimerEventHandler(glTextureManager::OnTimer),
742  NULL, this);
743  m_timer.Start(500);
744 }
745 
746 glTextureManager::~glTextureManager() {
747  // ClearAllRasterTextures();
748  ClearJobList();
749  for (int i = 0; i < m_max_jobs; i++) {
750  delete(progList[i]);
751  }
752  progList.Clear();
753  for(auto hash : m_chart_texfactory_hash) {
754  delete hash.second;
755  }
756  m_chart_texfactory_hash.clear();
757 }
758 
759 #define NBAR_LENGTH 40
760 
761 void glTextureManager::OnEvtThread(OCPN_CompressionThreadEvent &event) {
762  JobTicket *ticket = event.GetTicket();
763 
764  if (event.type == 1) {
765  if (!m_progDialog) {
766  // currently unreachable, but...
767  return;
768  }
769  // Look for a matching entry...
770  bool bfound = false;
771  ProgressInfoItem *item;
772  wxProgressInfoListNode *tnode = progList.GetFirst();
773  while (tnode) {
774  item = tnode->GetData();
775  if (item->file_path == ticket->m_ChartPath) {
776  bfound = true;
777  break;
778  }
779  tnode = tnode->GetNext();
780  }
781 
782  if (!bfound) {
783  // look for an empty slot
784  tnode = progList.GetFirst();
785  while (tnode) {
786  item = tnode->GetData();
787  if (item->file_path.IsEmpty()) {
788  bfound = true;
789  item->file_path = ticket->m_ChartPath;
790  break;
791  }
792  tnode = tnode->GetNext();
793  }
794  }
795 
796  if (bfound) {
797  wxString msgx;
798  if (1) {
799  int bar_length = NBAR_LENGTH;
800  if (m_bcompact) bar_length = 20;
801 
802  msgx += _T("\n[");
803  wxString block = wxString::Format(_T("%c"), 0x2588);
804  float cutoff = -1.;
805  if (event.nstat_max != 0)
806  cutoff = ((event.nstat + 1) / (float)event.nstat_max) * bar_length;
807  for (int i = 0; i < bar_length; i++) {
808  if (i <= cutoff)
809  msgx += block;
810  else
811  msgx += _T("-");
812  }
813  msgx += _T("]");
814 
815  if (!m_bcompact) {
816  wxString msgy;
817  msgy.Printf(_T(" [%3d/%3d] "), event.nstat + 1, event.nstat_max);
818  msgx += msgy;
819 
820  wxFileName fn(ticket->m_ChartPath);
821  msgx += fn.GetFullName();
822  }
823  } else
824  msgx.Printf(_T("\n %3d/%3d"), event.nstat + 1, event.nstat_max);
825 
826  item->msgx = msgx;
827  }
828 
829  // Ready to compose
830  wxString msg;
831  tnode = progList.GetFirst();
832  while (tnode) {
833  item = tnode->GetData();
834  msg += item->msgx + _T("\n");
835  tnode = tnode->GetNext();
836  }
837 
838  if (m_skipout) m_progMsg = _T("Skipping, please wait...\n\n");
839 
840  if (!m_progDialog->Update(m_jcnt, m_progMsg + msg, &m_skip)) m_skip = true;
841  if (m_skip) m_skipout = true;
842  return;
843  }
844 
845  if (ticket->b_isaborted || ticket->b_abort) {
846  for (int i = 0; i < g_mipmap_max_level + 1; i++) {
847  free(ticket->comp_bits_array[i]);
848  free(ticket->compcomp_bits_array[i]);
849  }
850 
851  if (bthread_debug)
852  printf(
853  " Abort job: %08X Jobs running: %d Job count: %lu "
854  "\n",
855  ticket->ident, GetRunningJobCount(),
856  (unsigned long)todo_list.GetCount());
857  } else if (!ticket->b_inCompressAll) {
858  // Normal completion from here
859  glTextureDescriptor *ptd = ticket->pFact->GetpTD(ticket->m_rect);
860  if (ptd) {
861  for (int i = 0; i < g_mipmap_max_level + 1; i++)
862  ptd->comp_array[i] = ticket->comp_bits_array[i];
863 
864  if (ticket->bpost_zip_compress) {
865  for (int i = 0; i < g_mipmap_max_level + 1; i++) {
866  ptd->compcomp_array[i] = ticket->compcomp_bits_array[i];
867  ptd->compcomp_size[i] = ticket->compcomp_size_array[i];
868  }
869  }
870 
871  // We need to force a refresh to replace the uncompressed texture
872  // This frees video memory and is also really required if we had
873  // gone up a mipmap level
874  gFrame->InvalidateAllGL();
875  ptd->compdata_ticks = 10;
876  }
877 
878  if (bthread_debug)
879  printf(
880  " Finished job: %08X Jobs running: %d Job count: %lu "
881  " \n",
882  ticket->ident, GetRunningJobCount(),
883  (unsigned long)todo_list.GetCount());
884  }
885 
886  // Free all possible memory
887  if (ticket->b_inCompressAll) { // if compressing all write cache here
888  ChartBase *pchart =
889  ChartData->OpenChartFromDB(ticket->m_ChartPath, FULL_INIT);
890  ChartData->DeleteCacheChart(pchart);
891  delete ticket->pFact;
892  }
893 
894  wxProgressInfoListNode *tnode = progList.GetFirst();
895  while (tnode) {
896  ProgressInfoItem *item = tnode->GetData();
897  if (item->file_path == ticket->m_ChartPath) item->file_path = _T("");
898  tnode = tnode->GetNext();
899  }
900 
901  if (g_raster_format != GL_COMPRESSED_RGB_FXT1_3DFX) {
902  running_list.DeleteObject(ticket);
903  StartTopJob();
904  }
905 
906  delete ticket;
907 }
908 
909 void glTextureManager::OnTimer(wxTimerEvent &event) {
910  m_ticks++;
911 
912  // Scrub all the TD's, looking for any completed compression jobs
913  // that have finished
914  // In the interest of not disturbing the GUI, process only one TD per tick
915  if (g_GLOptions.m_bTextureCompression) {
916  for (ChartPathHashTexfactType::iterator itt =
917  m_chart_texfactory_hash.begin();
918  itt != m_chart_texfactory_hash.end(); ++itt) {
919  glTexFactory *ptf = itt->second;
920  if (ptf && ptf->OnTimer()) {
921  // break;
922  }
923  }
924  }
925 
926 #if 0
927  if((m_ticks % 4/*120*/) == 0){
928 
929  // inventory
930  int mem_total, mem_used;
931  GetMemoryStatus(&mem_total, &mem_used);
932 
933  int map_size = 0;
934  int comp_size = 0;
935  int compcomp_size = 0;
936 
937  for(ChartPathHashTexfactType::iterator itt = m_chart_texfactory_hash.begin();
938  itt != m_chart_texfactory_hash.end(); ++itt ) {
939  glTexFactory *ptf = itt->second;
940 
941  ptf->AccumulateMemStatistics(map_size, comp_size, compcomp_size);
942  }
943 
944  int m1 = 1024 * 1024;
945 // wxString path = wxFileName(m_ChartPath).GetName();
946  printf("%6d %6ld Map: %10d Comp:%10d CompComp: %10d \n", mem_used/1024, g_tex_mem_used/m1, map_size, comp_size, compcomp_size);//, path.mb_str().data());
947 
949  }
950 #endif
951 }
952 
953 bool glTextureManager::ScheduleJob(glTexFactory *client, const wxRect &rect,
954  int level, bool b_throttle_thread,
955  bool b_nolimit, bool b_postZip,
956  bool b_inplace) {
957  wxString chart_path = client->GetChartPath();
958  if (!b_nolimit) {
959  if (todo_list.GetCount() >= 50) {
960  // remove last job which is least important
961  wxJobListNode *node = todo_list.GetLast();
962  JobTicket *ticket = node->GetData();
963  todo_list.DeleteNode(node);
964  delete ticket;
965  }
966 
967  // Avoid adding duplicate jobs, i.e. the same chart_path, and the same
968  // rectangle
969  wxJobListNode *node = todo_list.GetFirst();
970  while (node) {
971  JobTicket *ticket = node->GetData();
972  if ((ticket->m_ChartPath == chart_path) && (ticket->m_rect == rect)) {
973  // bump to front
974  todo_list.DeleteNode(node);
975  todo_list.Insert(ticket);
976  ticket->level_min_request = level;
977  return false;
978  }
979 
980  node = node->GetNext();
981  }
982 
983  // avoid duplicate worker jobs
984  wxJobListNode *tnode = running_list.GetFirst();
985  while (tnode) {
986  JobTicket *ticket = tnode->GetData();
987  if (ticket->m_rect == rect && ticket->m_ChartPath == chart_path) {
988  return false;
989  }
990  tnode = tnode->GetNext();
991  }
992  }
993 
994  JobTicket *pt = new JobTicket;
995  pt->pFact = client;
996  pt->m_rect = rect;
997  pt->level_min_request = level;
998  glTextureDescriptor *ptd = client->GetOrCreateTD(pt->m_rect);
999  pt->ident = (ptd->tex_name << 16) + level;
1000  pt->b_throttle = b_throttle_thread;
1001  pt->m_ChartPath = chart_path;
1002 
1003  pt->level0_bits = NULL;
1004  pt->b_abort = false;
1005  pt->b_isaborted = false;
1006  pt->bpost_zip_compress = b_postZip;
1007  pt->binplace = b_inplace;
1008  pt->b_inCompressAll = b_inCompressAllCharts;
1009 
1010  /* do we compress in ram using builtin libraries, or do we
1011  upload to the gpu and use the driver to perform compression?
1012  we have builtin libraries for DXT1 (squish) and ETC1 (etcpak)
1013  FXT1 must use the driver, ETC1 cannot, and DXT1 can use the driver
1014  but the results are worse and don't compress well.
1015 
1016  additionally, if we use the driver we must stay single threaded in this thread
1017  (unless we created multiple opengl contexts), but with with our own libraries,
1018  we can use multiple threads to take advantage of multiple cores */
1019 
1020  if (g_raster_format != GL_COMPRESSED_RGB_FXT1_3DFX) {
1021  todo_list.Insert(pt); // push to front as a stack
1022  if (bthread_debug) {
1023  int mem_used;
1024  GetMemoryStatus(0, &mem_used);
1025  printf("Adding job: %08X Job Count: %lu mem_used %d\n", pt->ident,
1026  (unsigned long)todo_list.GetCount(), mem_used);
1027  }
1028 
1029  StartTopJob();
1030  } else {
1031  // give level 0 buffer to the ticket
1032  pt->level0_bits = ptd->map_array[0];
1033  ptd->map_array[0] = NULL;
1034 
1035  pt->DoJob();
1036 
1037  OCPN_CompressionThreadEvent Nevent(wxEVT_OCPN_COMPRESSIONTHREAD, 0);
1038  Nevent.type = 0;
1039  Nevent.SetTicket(pt);
1040  ProcessEventLocally(Nevent);
1041  // from here m_ticket is undefined (if deleted in event handler)
1042  }
1043  return true;
1044 }
1045 
1046 bool glTextureManager::StartTopJob() {
1047  wxJobListNode *node = todo_list.GetFirst();
1048  if (!node) return false;
1049 
1050  JobTicket *ticket = node->GetData();
1051 
1052  // Is it possible to start another job?
1053  if (GetRunningJobCount() >= wxMax(m_max_jobs - ticket->b_throttle, 1))
1054  return false;
1055 
1056  todo_list.DeleteNode(node);
1057 
1058  glTextureDescriptor *ptd = ticket->pFact->GetpTD(ticket->m_rect);
1059  // don't need the job if we already have the compressed data
1060  if (ptd->comp_array[0]) {
1061  delete ticket;
1062  return StartTopJob();
1063  }
1064 
1065  if (ptd->map_array[0]) {
1066  if (ticket->level_min_request == 0) {
1067  // give level 0 buffer to the ticket
1068  ticket->level0_bits = ptd->map_array[0];
1069  ptd->map_array[0] = NULL;
1070  } else {
1071  // would be nicer to use reference counters
1072  int size = TextureTileSize(0, false);
1073  ticket->level0_bits = (unsigned char *)malloc(size);
1074  memcpy(ticket->level0_bits, ptd->map_array[0], size);
1075  }
1076  }
1077 
1078  running_list.Append(ticket);
1079  DoThreadJob(ticket);
1080 
1081  return true;
1082 }
1083 
1084 bool glTextureManager::DoThreadJob(JobTicket *pticket) {
1085  if (bthread_debug)
1086  printf(" Starting job: %08X Jobs running: %d Jobs left: %lu\n",
1087  pticket->ident, GetRunningJobCount(),
1088  (unsigned long)todo_list.GetCount());
1089 
1092  CompressionPoolThread *t = new CompressionPoolThread(pticket, this);
1093  pticket->pthread = t;
1094 
1095  t->Run();
1096 
1097  return true;
1098 }
1099 
1100 bool glTextureManager::AsJob(wxString const &chart_path) const {
1101  if (chart_path.Len()) {
1102  wxJobListNode *tnode = running_list.GetFirst();
1103  while (tnode) {
1104  JobTicket *ticket = tnode->GetData();
1105  if (ticket->m_ChartPath.IsSameAs(chart_path)) {
1106  return true;
1107  }
1108  tnode = tnode->GetNext();
1109  }
1110  }
1111  return false;
1112 }
1113 
1114 void glTextureManager::PurgeJobList(wxString chart_path) {
1115  if (chart_path.Len()) {
1116  // Remove all pending jobs relating to the passed chart path
1117  wxJobListNode *next, *tnode = todo_list.GetFirst();
1118  while (tnode) {
1119  JobTicket *ticket = tnode->GetData();
1120  next = tnode->GetNext();
1121  if (ticket->m_ChartPath.IsSameAs(chart_path)) {
1122  if (bthread_debug)
1123  printf("Pool: Purge pending job for purged chart\n");
1124  todo_list.DeleteNode(tnode);
1125  delete ticket;
1126  }
1127  tnode = next;
1128  }
1129 
1130  wxJobListNode *node = running_list.GetFirst();
1131  while (node) {
1132  JobTicket *ticket = node->GetData();
1133  if (ticket->m_ChartPath.IsSameAs(chart_path)) {
1134  ticket->b_abort = true;
1135  }
1136  node = node->GetNext();
1137  }
1138 
1139  if (bthread_debug)
1140  printf("Pool: Purge, todo count: %lu\n",
1141  (long unsigned)todo_list.GetCount());
1142  } else {
1143  wxJobListNode *node = todo_list.GetFirst();
1144  while (node) {
1145  JobTicket *ticket = node->GetData();
1146  delete ticket;
1147  node = node->GetNext();
1148  }
1149  todo_list.Clear();
1150  // Mark all running tasks for "abort"
1151  node = running_list.GetFirst();
1152  while (node) {
1153  JobTicket *ticket = node->GetData();
1154  ticket->b_abort = true;
1155  node = node->GetNext();
1156  }
1157  }
1158 }
1159 
1160 void glTextureManager::ClearJobList() {
1161  wxJobListNode *node = todo_list.GetFirst();
1162  while (node) {
1163  JobTicket *ticket = node->GetData();
1164  delete ticket;
1165  node = node->GetNext();
1166  }
1167  todo_list.Clear();
1168 }
1169 
1170 void glTextureManager::ClearAllRasterTextures(void) {
1171  // Delete all the TexFactory instances
1172  ChartPathHashTexfactType::iterator itt;
1173  for (itt = m_chart_texfactory_hash.begin();
1174  itt != m_chart_texfactory_hash.end(); ++itt) {
1175  glTexFactory *ptf = itt->second;
1176 
1177  delete ptf;
1178  }
1179  m_chart_texfactory_hash.clear();
1180 
1181  if (g_tex_mem_used != 0)
1182  wxLogMessage(_T("Texture memory use calculation error\n"));
1183 }
1184 
1185 bool glTextureManager::PurgeChartTextures(ChartBase *pc, bool b_purge_factory) {
1186  // Look for the texture factory for this chart
1187  ChartPathHashTexfactType::iterator ittf =
1188  m_chart_texfactory_hash.find(pc->GetHashKey());
1189 
1190  // Found ?
1191  if (ittf != m_chart_texfactory_hash.end()) {
1192  glTexFactory *pTexFact = ittf->second;
1193 
1194  if (pTexFact) {
1195  if (b_purge_factory) {
1196  m_chart_texfactory_hash.erase(ittf); // This chart becoming invalid
1197 
1198  delete pTexFact;
1199  }
1200 
1201  return true;
1202  } else {
1203  m_chart_texfactory_hash.erase(ittf);
1204  return false;
1205  }
1206  } else
1207  return false;
1208 }
1209 
1210 bool glTextureManager::TextureCrunch(double factor) {
1211  double hysteresis = 0.90;
1212 
1213  bool bGLMemCrunch =
1214  g_tex_mem_used >
1215  (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) * factor;
1216  if (!bGLMemCrunch) return false;
1217 
1218  ChartPathHashTexfactType::iterator it0;
1219  for (it0 = m_chart_texfactory_hash.begin();
1220  it0 != m_chart_texfactory_hash.end(); ++it0) {
1221  glTexFactory *ptf = it0->second;
1222  if (!ptf) continue;
1223  wxString chart_full_path = ptf->GetChartPath();
1224 
1225  bGLMemCrunch = g_tex_mem_used >
1226  (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) *
1227  factor * hysteresis;
1228  if (!bGLMemCrunch) break;
1229 
1230  // For each canvas
1231  for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1232  ChartCanvas *cc = g_canvasArray.Item(i);
1233  if (cc) {
1234  if (cc->GetVP().b_quilt) // quilted
1235  {
1236  if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1237  !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1238  ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1239  1024 * factor * hysteresis);
1240  }
1241  } else // not quilted
1242  {
1243  if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1244  ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1245  1024 * factor * hysteresis);
1246  }
1247  }
1248  }
1249  }
1250  }
1251 
1252  return true;
1253 }
1254 
1255 #define MAX_CACHE_FACTORY 50
1256 bool glTextureManager::FactoryCrunch(double factor) {
1257  if (m_chart_texfactory_hash.size() == 0) {
1258  /* nothing to free */
1259  return false;
1260  }
1261 
1262  int mem_used;
1263  GetMemoryStatus(0, &mem_used);
1264  double hysteresis = 0.90;
1265  ChartPathHashTexfactType::iterator it0;
1266 
1267  bool bMemCrunch =
1268  (g_memCacheLimit &&
1269  ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1270  mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1271  (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1272 
1273  if (!bMemCrunch) return false;
1274 
1275  // Need more, so delete the oldest factory
1276  // Find the oldest unused factory
1277  int lru_oldest = 2147483647;
1278  glTexFactory *ptf_oldest = NULL;
1279 
1280  for (it0 = m_chart_texfactory_hash.begin();
1281  it0 != m_chart_texfactory_hash.end(); ++it0) {
1282  glTexFactory *ptf = it0->second;
1283  if (!ptf) continue;
1284  wxString chart_full_path = ptf->GetChartPath();
1285 
1286  // we better have to find one because glTexFactory keep cache texture open
1287  // and ocpn will eventually run out of file descriptors
1288 
1289  // For each canvas
1290  for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1291  ChartCanvas *cc = g_canvasArray.Item(i);
1292  if (cc) {
1293  if (cc->GetVP().b_quilt) // quilted
1294  {
1295  if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1296  !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1297  int lru = ptf->GetLRUTime();
1298  if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1299  lru_oldest = lru;
1300  ptf_oldest = ptf;
1301  }
1302  }
1303  } else {
1304  if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1305  int lru = ptf->GetLRUTime();
1306  if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1307  lru_oldest = lru;
1308  ptf_oldest = ptf;
1309  }
1310  }
1311  }
1312  }
1313  }
1314  }
1315 
1316  // Found one?
1317  if (!ptf_oldest) return false;
1318 
1319  ptf_oldest->FreeSome(g_memCacheLimit * factor * hysteresis);
1320 
1321  GetMemoryStatus(0, &mem_used);
1322 
1323  bMemCrunch = (g_memCacheLimit &&
1324  ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1325  mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1326  (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1327 
1328  if (!bMemCrunch) return false;
1329 
1330  // Need more, so delete the oldest chart too
1331 
1332  m_chart_texfactory_hash.erase(
1333  ptf_oldest->GetHashKey()); // This chart becoming invalid
1334 
1335  delete ptf_oldest;
1336 
1337  return true;
1338 }
1339 
1340 void glTextureManager::BuildCompressedCache() {
1341  idx_sorted_by_distance.Clear();
1342 
1343  // Building the cache may take a long time....
1344  // Be a little smarter.
1345  // Build a sorted array of chart database indices, sorted on distance from the
1346  // ownship currently. This way, a user may build a few charts textures for
1347  // immediate use, then "skip" out on the rest until later.
1348  int count = 0;
1349  for (int i = 0; i < ChartData->GetChartTableEntries(); i++) {
1350  /* skip if not kap */
1351  const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1352  ChartTypeEnum chart_type = (ChartTypeEnum)cte.GetChartType();
1353  if (chart_type == CHART_TYPE_PLUGIN) {
1354  if (cte.GetChartFamily() != CHART_FAMILY_RASTER) continue;
1355  } else {
1356  if (chart_type != CHART_TYPE_KAP) continue;
1357  }
1358 
1359  wxString CompressedCacheFilePath =
1360  CompressedCachePath(ChartData->GetDBChartFileName(i));
1361  wxFileName fn(CompressedCacheFilePath);
1362  // if(fn.FileExists()) /* skip if file exists */
1363  // continue;
1364 
1365  idx_sorted_by_distance.Add(i);
1366 
1367  count++;
1368  }
1369 
1370  if (count == 0) return;
1371 
1372  wxLogMessage(
1373  wxString::Format(_T("BuildCompressedCache() count = %d"), count));
1374 
1375  m_timer.Stop();
1376  PurgeJobList();
1377  if (GetRunningJobCount()) {
1378  wxLogMessage(_T("Starting compressor pool drain"));
1379  wxDateTime now = wxDateTime::Now();
1380  time_t stall = now.GetTicks();
1381 #define THREAD_WAIT_SECONDS 5
1382  time_t end = stall + THREAD_WAIT_SECONDS;
1383 
1384  int n_comploop = 0;
1385  while (stall < end) {
1386  wxDateTime later = wxDateTime::Now();
1387  stall = later.GetTicks();
1388 
1389  wxString msg;
1390  msg.Printf(_T("Time: %d Job Count: %d"), n_comploop,
1391  GetRunningJobCount());
1392  wxLogMessage(msg);
1393  if (!GetRunningJobCount()) break;
1394  wxYield();
1395  wxSleep(1);
1396  }
1397 
1398  wxString fmsg;
1399  fmsg.Printf(_T("Finished compressor pool drain..Time: %d Job Count: %d"),
1400  n_comploop, GetRunningJobCount());
1401  wxLogMessage(fmsg);
1402  }
1403  ClearAllRasterTextures();
1404  b_inCompressAllCharts = true;
1405 
1406  // Build another array of sorted compression targets.
1407  // We need to do this, as the chart table will not be invariant
1408  // after the compression threads start, so our index array will be invalid.
1409 
1410  ArrayOfCompressTargets ct_array;
1411  for (unsigned int j = 0; j < idx_sorted_by_distance.GetCount(); j++) {
1412  int i = idx_sorted_by_distance[j];
1413 
1414  const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1415  double distance = chart_dist(i);
1416 
1417  wxString filename = cte.GetFullSystemPath();
1418 
1419  compress_target *pct = new compress_target;
1420  pct->distance = distance;
1421  pct->chart_path = filename;
1422 
1423  ct_array.Add(pct);
1424  }
1425 
1426  // create progress dialog
1427  long style = wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
1428  wxPD_REMAINING_TIME | wxPD_CAN_ABORT;
1429 
1430  wxString msg0;
1431  msg0 =
1432  _T(" ")
1433  _T(" \n \n ");
1434 
1435 #ifdef __WXQT__
1436  msg0 =
1437  _T("Very ")
1438  _T("longgggggggggggggggggggggggggggggggggggggggggggg\ngggggggggggggggggg")
1439  _T("gggggggggggggggggggggggggg top line ");
1440 #endif
1441 
1442  for (int i = 0; i < m_max_jobs + 1; i++)
1443  msg0 += _T("\n ");
1444 
1445  m_progDialog = new wxGenericProgressDialog();
1446 
1447  wxFont *qFont = GetOCPNScaledFont(_("Dialog"));
1448  int fontSize = qFont->GetPointSize();
1449  wxFont *sFont;
1450  wxSize csz = gFrame->GetClientSize();
1451  if (csz.x < 500 || csz.y < 500)
1452  sFont = FontMgr::Get().FindOrCreateFont(
1453  10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1454  else
1455  sFont = FontMgr::Get().FindOrCreateFont(fontSize, wxFONTFAMILY_TELETYPE,
1456  wxFONTSTYLE_NORMAL,
1457  wxFONTWEIGHT_NORMAL);
1458 
1459  m_progDialog->SetFont(*sFont);
1460 
1461  // Should we use "compact" screen layout?
1462  wxScreenDC sdc;
1463  int height, width;
1464  sdc.GetTextExtent(_T("[WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW]"), &width, &height,
1465  NULL, NULL, sFont);
1466  if (width > (csz.x / 2)) m_bcompact = true;
1467 
1468  m_progDialog->Create(_("OpenCPN Compressed Cache Update"), msg0, count + 1,
1469  NULL, style);
1470 
1471  // Make sure the dialog is big enough to be readable
1472  m_progDialog->Hide();
1473  wxSize sz = m_progDialog->GetSize();
1474  sz.x = csz.x * 9 / 10;
1475  m_progDialog->SetSize(sz);
1476 
1477  m_progDialog->Layout();
1478  wxSize sza = m_progDialog->GetSize();
1479 
1480  m_progDialog->Centre();
1481  m_progDialog->Show();
1482  m_progDialog->Raise();
1483 
1484  m_skipout = false;
1485  m_skip = false;
1486  int yield = 0;
1487 
1488  for (m_jcnt = 0; m_jcnt < ct_array.GetCount(); m_jcnt++) {
1489  wxString filename = ct_array[m_jcnt].chart_path;
1490  wxString CompressedCacheFilePath = CompressedCachePath(filename);
1491  double distance = ct_array[m_jcnt].distance;
1492 
1493  ChartBase *pchart = ChartData->OpenChartFromDBAndLock(filename, FULL_INIT);
1494  if (!pchart) /* probably a corrupt chart */
1495  continue;
1496 
1497  // bad things if more than one texfactory for a chart
1498  g_glTextureManager->PurgeChartTextures(pchart, true);
1499 
1500  ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pchart);
1501  if (pBSBChart == 0) continue;
1502 
1503  glTexFactory *tex_fact = new glTexFactory(pchart, g_raster_format);
1504 
1505  m_progMsg.Printf(_("Distance from Ownship: %4.0f NMi"), distance);
1506  m_progMsg += "\n";
1507  m_progMsg.Prepend(_T("Preparing RNC Cache...\n"));
1508 
1509  if (m_skipout) {
1510  g_glTextureManager->PurgeJobList();
1511  ChartData->DeleteCacheChart(pchart);
1512  delete tex_fact;
1513  break;
1514  }
1515 
1516  int size_X = pBSBChart->GetSize_X();
1517  int size_Y = pBSBChart->GetSize_Y();
1518 
1519  int tex_dim = g_GLOptions.m_iTextureDimension;
1520 
1521  int nx_tex = ceil((float)size_X / tex_dim);
1522  int ny_tex = ceil((float)size_Y / tex_dim);
1523 
1524  wxRect rect;
1525  rect.y = 0;
1526  rect.width = tex_dim;
1527  rect.height = tex_dim;
1528  for (int y = 0; y < ny_tex; y++) {
1529  rect.x = 0;
1530  for (int x = 0; x < nx_tex; x++) {
1531  for (int level = 0; level < g_mipmap_max_level + 1; level++) {
1532  if (!tex_fact->IsLevelInCache(level, rect, global_color_scheme)) {
1533  goto schedule;
1534  }
1535  }
1536  rect.x += rect.width;
1537  }
1538  rect.y += rect.height;
1539  }
1540  // Nothing to do
1541  // Free all possible memory
1542  ChartData->DeleteCacheChart(pchart);
1543  delete tex_fact;
1544  yield++;
1545  if (yield == 200) {
1546  ::wxYield();
1547  yield = 0;
1548  if (!m_progDialog->Update(m_jcnt)) {
1549  m_skip = true;
1550  m_skipout = true;
1551  }
1552  }
1553  continue;
1554 
1555  // some work to do
1556  schedule:
1557 
1558  yield = 0;
1559  ScheduleJob(tex_fact, wxRect(), 0, false, true, true, false);
1560  while (!m_skip) {
1561  ::wxYield();
1562  int cnt = GetJobCount() - GetRunningJobCount();
1563  if (!cnt) break;
1564  wxThread::Sleep(1);
1565  }
1566 
1567  if (m_skipout) {
1568  g_glTextureManager->PurgeJobList();
1569  ChartData->DeleteCacheChart(pchart);
1570  delete tex_fact;
1571  break;
1572  }
1573  }
1574 
1575  while (GetRunningJobCount()) {
1576  wxThread::Sleep(1);
1577  ::wxYield();
1578  }
1579 
1580  b_inCompressAllCharts = false;
1581  m_timer.Start(500);
1582 
1583  delete m_progDialog;
1584  m_progDialog = nullptr;
1585 }
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
General purpose GUI support.
Runtime representation of a plugin block.