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