OpenCPN Partial API docs
Loading...
Searching...
No Matches
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>
65WX_DEFINE_LIST(JobList);
66WX_DEFINE_LIST(ProgressInfoList);
67
68WX_DEFINE_ARRAY_PTR(ChartCanvas *, arrayofCanvasPtr);
69
70extern int g_mipmap_max_level;
71extern GLuint g_raster_format;
72extern int g_memCacheLimit;
73extern ChartDB *ChartData;
74extern ocpnGLOptions g_GLOptions;
75extern long g_tex_mem_used;
76extern int g_tile_size;
77extern int g_uncompressed_tile_size;
78extern int g_nCPUCount;
79
80extern bool b_inCompressAllCharts;
81extern MyFrame *gFrame;
82extern arrayofCanvasPtr g_canvasArray;
83
84extern OCPNPlatform *g_Platform;
85extern ColorScheme global_color_scheme;
86
87extern bool GetMemoryStatus(int *mem_total, int *mem_used);
88
89bool bthread_debug;
90bool g_throttle_squish;
91
92glTextureManager *g_glTextureManager;
93
94#include "ssl/sha1.h"
95
96wxString 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
124int g_mipmap_max_level = 4;
125
126#if 0
127OCPN_CompressProgressEvent::OCPN_CompressProgressEvent(wxEventType commandType, int id)
128:wxEvent(id, commandType)
129{
130}
131
132OCPN_CompressProgressEvent::~OCPN_CompressProgressEvent()
133{
134}
135
136wxEvent* 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
146static 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
171WX_DEFINE_SORTED_ARRAY_INT(int, MySortedArrayInt);
172int CompareInts(int n1, int n2) {
173 double d1 = chart_dist(n1);
174 double d2 = chart_dist(n2);
175 return (int)(d1 - d2);
176}
177
178static MySortedArrayInt idx_sorted_by_distance(CompareInts);
179
181public:
182 wxString chart_path;
183 double distance;
184};
185
186#include <wx/arrimpl.cpp>
187
188WX_DECLARE_OBJARRAY(compress_target, ArrayOfCompressTargets);
189// WX_DEFINE_OBJARRAY(ArrayOfCompressTargets);
190
191JobTicket::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. */
204static
205void 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 */
229static 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
250static 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
290static 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
320void 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
347int 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
353int 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
371bool 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
425class OCPNStopWatch
426{
427public:
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
437private:
438 timespec tp;
439};
440#else
441class OCPNStopWatch : public wxStopWatch {};
442#endif
443
444static 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
454static wxMutex s_mutexProtectingChartBitRead;
455
456bool 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__
596class SE_Exception {
597private:
598 unsigned int nSE;
599
600public:
601 SE_Exception() {}
602 SE_Exception(unsigned int n) : nSE(n) {}
603 ~SE_Exception() {}
604 unsigned int getSeNumber() { return nSE; }
605};
606
607void my_translate(unsigned int code, _EXCEPTION_POINTERS *ep) {
608 throw SE_Exception();
609}
610#endif
611
612OCPN_CompressionThreadEvent::OCPN_CompressionThreadEvent(
613 wxEventType commandType, int id)
614 : wxEvent(id, commandType) {
615 type = 0;
616}
617
618OCPN_CompressionThreadEvent::~OCPN_CompressionThreadEvent() {}
619
620wxEvent *OCPN_CompressionThreadEvent::Clone() const {
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
658CompressionPoolThread::CompressionPoolThread(JobTicket *ticket,
659 wxEvtHandler *message_target) {
660 m_pMessageTarget = message_target;
661 m_ticket = ticket;
662
663 Create();
664}
665
666void *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
711glTextureManager::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
746glTextureManager::~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
761void 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
909void 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
953bool 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
1046bool 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
1084bool 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
1100bool 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
1114void 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
1160void 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
1170void 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
1185bool 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
1210bool 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
1256bool 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
1340void 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
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.
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:131
Base class for all chart types.
Definition chartbase.h:119
Chart display canvas.
Definition chcanv.h:135
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition FontMgr.cpp:450
Main application frame.
Definition ocpn_frame.h:136
Provides platform-specific support utilities for OpenCPN.
wxFont * GetOCPNScaledFont(wxString item, int default_size)
Retrieves a font from FontMgr, optionally scaled for physical readability.
Definition gui_lib.cpp:54
General purpose GUI support.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:181
Runtime representation of a plugin block.