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 // Mark running tasks for this chart as "abort"
1109 for (auto node = running_list.begin(); node != running_list.end(); ++node) {
1110 JobTicket *ticket = *node;
1111 if (ticket->m_ChartPath == chart_path) ticket->b_abort = true;
1112 }
1113
1114 if (bthread_debug)
1115 std::cout << "Pool: Purge, todo count: " << list.size() << "\n";
1116 } else {
1117 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
1118 JobTicket *ticket = *node;
1119 delete ticket;
1120 }
1121 todo_list.clear();
1122 // Mark all running tasks for "abort"
1123 for (auto node = running_list.begin(); node != running_list.end(); ++node) {
1124 JobTicket *ticket = *node;
1125 ticket->b_abort = true;
1126 }
1127 }
1128}
1129
1130void glTextureManager::ClearJobList() {
1131 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
1132 JobTicket *ticket = *node;
1133 delete ticket;
1134 }
1135 todo_list.clear();
1136}
1137
1138void glTextureManager::ClearAllRasterTextures() {
1139 // Delete all the TexFactory instances
1140 ChartPathHashTexfactType::iterator itt;
1141 for (itt = m_chart_texfactory_hash.begin();
1142 itt != m_chart_texfactory_hash.end(); ++itt) {
1143 glTexFactory *ptf = itt->second;
1144
1145 delete ptf;
1146 }
1147 m_chart_texfactory_hash.clear();
1148
1149 if (g_tex_mem_used != 0)
1150 wxLogMessage("Texture memory use calculation error\n");
1151}
1152
1153bool glTextureManager::PurgeChartTextures(ChartBase *pc, bool b_purge_factory) {
1154 // Look for the texture factory for this chart
1155 ChartPathHashTexfactType::iterator ittf =
1156 m_chart_texfactory_hash.find(pc->GetHashKey());
1157
1158 // Found ?
1159 if (ittf != m_chart_texfactory_hash.end()) {
1160 glTexFactory *pTexFact = ittf->second;
1161
1162 if (pTexFact) {
1163 if (b_purge_factory) {
1164 m_chart_texfactory_hash.erase(ittf); // This chart becoming invalid
1165
1166 delete pTexFact;
1167 }
1168
1169 return true;
1170 } else {
1171 m_chart_texfactory_hash.erase(ittf);
1172 return false;
1173 }
1174 } else
1175 return false;
1176}
1177
1178bool glTextureManager::TextureCrunch(double factor) {
1179 double hysteresis = 0.90;
1180
1181 bool bGLMemCrunch =
1182 g_tex_mem_used >
1183 (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) * factor;
1184 if (!bGLMemCrunch) return false;
1185
1186 ChartPathHashTexfactType::iterator it0;
1187 for (it0 = m_chart_texfactory_hash.begin();
1188 it0 != m_chart_texfactory_hash.end(); ++it0) {
1189 glTexFactory *ptf = it0->second;
1190 if (!ptf) continue;
1191 wxString chart_full_path = ptf->GetChartPath();
1192
1193 bGLMemCrunch = g_tex_mem_used >
1194 (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) *
1195 factor * hysteresis;
1196 if (!bGLMemCrunch) break;
1197
1198 // For each canvas
1199 for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1200 ChartCanvas *cc = g_canvasArray.Item(i);
1201 if (cc) {
1202 if (cc->GetVP().b_quilt) // quilted
1203 {
1204 if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1205 !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1206 ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1207 1024 * factor * hysteresis);
1208 }
1209 } else // not quilted
1210 {
1211 if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1212 ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1213 1024 * factor * hysteresis);
1214 }
1215 }
1216 }
1217 }
1218 }
1219
1220 return true;
1221}
1222
1223#define MAX_CACHE_FACTORY 50
1224bool glTextureManager::FactoryCrunch(double factor) {
1225 if (m_chart_texfactory_hash.size() == 0) {
1226 /* nothing to free */
1227 return false;
1228 }
1229
1230 int mem_used;
1231 platform::GetMemoryStatus(0, &mem_used);
1232 double hysteresis = 0.90;
1233 ChartPathHashTexfactType::iterator it0;
1234
1235 bool bMemCrunch =
1236 (g_memCacheLimit &&
1237 ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1238 mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1239 (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1240
1241 if (!bMemCrunch) return false;
1242
1243 // Need more, so delete the oldest factory
1244 // Find the oldest unused factory
1245 int lru_oldest = 2147483647;
1246 glTexFactory *ptf_oldest = NULL;
1247
1248 for (it0 = m_chart_texfactory_hash.begin();
1249 it0 != m_chart_texfactory_hash.end(); ++it0) {
1250 glTexFactory *ptf = it0->second;
1251 if (!ptf) continue;
1252 wxString chart_full_path = ptf->GetChartPath();
1253
1254 // we better have to find one because glTexFactory keep cache texture open
1255 // and ocpn will eventually run out of file descriptors
1256
1257 // For each canvas
1258 for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1259 ChartCanvas *cc = g_canvasArray.Item(i);
1260 if (cc) {
1261 if (cc->GetVP().b_quilt) // quilted
1262 {
1263 if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1264 !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1265 int lru = ptf->GetLRUTime();
1266 if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1267 lru_oldest = lru;
1268 ptf_oldest = ptf;
1269 }
1270 }
1271 } else {
1272 if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1273 int lru = ptf->GetLRUTime();
1274 if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1275 lru_oldest = lru;
1276 ptf_oldest = ptf;
1277 }
1278 }
1279 }
1280 }
1281 }
1282 }
1283
1284 // Found one?
1285 if (!ptf_oldest) return false;
1286
1287 ptf_oldest->FreeSome(g_memCacheLimit * factor * hysteresis);
1288
1289 platform::GetMemoryStatus(0, &mem_used);
1290
1291 bMemCrunch = (g_memCacheLimit &&
1292 ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1293 mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1294 (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1295
1296 if (!bMemCrunch) return false;
1297
1298 // Need more, so delete the oldest chart too
1299
1300 m_chart_texfactory_hash.erase(
1301 ptf_oldest->GetHashKey()); // This chart becoming invalid
1302
1303 delete ptf_oldest;
1304
1305 return true;
1306}
1307
1308void glTextureManager::BuildCompressedCache() {
1309 idx_sorted_by_distance.Clear();
1310
1311 // Building the cache may take a long time....
1312 // Be a little smarter.
1313 // Build a sorted array of chart database indices, sorted on distance from the
1314 // ownship currently. This way, a user may build a few charts textures for
1315 // immediate use, then "skip" out on the rest until later.
1316 int count = 0;
1317 for (int i = 0; i < ChartData->GetChartTableEntries(); i++) {
1318 /* skip if not kap */
1319 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1320 ChartTypeEnum chart_type = (ChartTypeEnum)cte.GetChartType();
1321 if (chart_type == CHART_TYPE_PLUGIN) {
1322 if (cte.GetChartFamily() != CHART_FAMILY_RASTER) continue;
1323 } else {
1324 if (chart_type != CHART_TYPE_KAP) continue;
1325 }
1326
1327 wxString CompressedCacheFilePath =
1328 CompressedCachePath(ChartData->GetDBChartFileName(i));
1329 wxFileName fn(CompressedCacheFilePath);
1330 // if(fn.FileExists()) /* skip if file exists */
1331 // continue;
1332
1333 idx_sorted_by_distance.Add(i);
1334
1335 count++;
1336 }
1337
1338 if (count == 0) return;
1339
1340 wxLogMessage(wxString::Format("BuildCompressedCache() count = %d", count));
1341
1342 m_timer.Stop();
1343 PurgeJobList();
1344 if (GetRunningJobCount()) {
1345 wxLogMessage("Starting compressor pool drain");
1346 wxDateTime now = wxDateTime::Now();
1347 time_t stall = now.GetTicks();
1348#define THREAD_WAIT_SECONDS 5
1349 time_t end = stall + THREAD_WAIT_SECONDS;
1350
1351 int n_comploop = 0;
1352 while (stall < end) {
1353 wxDateTime later = wxDateTime::Now();
1354 stall = later.GetTicks();
1355
1356 wxString msg;
1357 msg.Printf("Time: %d Job Count: %d", n_comploop, GetRunningJobCount());
1358 wxLogMessage(msg);
1359 if (!GetRunningJobCount()) break;
1360 wxYield();
1361 wxSleep(1);
1362 }
1363
1364 wxString fmsg;
1365 fmsg.Printf("Finished compressor pool drain..Time: %d Job Count: %d",
1366 n_comploop, GetRunningJobCount());
1367 wxLogMessage(fmsg);
1368 }
1369 ClearAllRasterTextures();
1370 b_inCompressAllCharts = true;
1371
1372 // Build another array of sorted compression targets.
1373 // We need to do this, as the chart table will not be invariant
1374 // after the compression threads start, so our index array will be invalid.
1375
1376 ArrayOfCompressTargets ct_array;
1377 for (unsigned int j = 0; j < idx_sorted_by_distance.GetCount(); j++) {
1378 int i = idx_sorted_by_distance[j];
1379
1380 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1381 double distance = chart_dist(i);
1382
1383 wxString filename = cte.GetFullSystemPath();
1384
1386 pct->distance = distance;
1387 pct->chart_path = filename;
1388
1389 ct_array.Add(pct);
1390 }
1391
1392 // create progress dialog
1393 long style = wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
1394 wxPD_REMAINING_TIME | wxPD_CAN_ABORT;
1395
1396 wxString msg0;
1397 msg0 =
1398 " "
1399 " \n \n ";
1400
1401#ifdef __WXQT__
1402 msg0 =
1403 "Very "
1404 "longgggggggggggggggggggggggggggggggggggggggggggg\ngggggggggggggggggg"
1405 "gggggggggggggggggggggggggg top line ";
1406#endif
1407
1408 for (int i = 0; i < m_max_jobs + 1; i++)
1409 msg0 += "\n ";
1410
1411 m_progDialog = new wxGenericProgressDialog();
1412
1413 wxFont *qFont = GetOCPNScaledFont(_("Dialog"));
1414 int fontSize = qFont->GetPointSize();
1415 wxFont *sFont;
1416 wxSize csz = wxTheApp->GetTopWindow()->GetClientSize();
1417 if (csz.x < 500 || csz.y < 500)
1418 sFont = FontMgr::Get().FindOrCreateFont(
1419 10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1420 else
1421 sFont = FontMgr::Get().FindOrCreateFont(fontSize, wxFONTFAMILY_TELETYPE,
1422 wxFONTSTYLE_NORMAL,
1423 wxFONTWEIGHT_NORMAL);
1424
1425 m_progDialog->SetFont(*sFont);
1426
1427 // Should we use "compact" screen layout?
1428 wxScreenDC sdc;
1429 int height, width;
1430 sdc.GetTextExtent("[WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW]", &width, &height, NULL,
1431 NULL, sFont);
1432 if (width > (csz.x / 2)) m_bcompact = true;
1433
1434 m_progDialog->Create(_("OpenCPN Compressed Cache Update"), msg0, count + 1,
1435 NULL, style);
1436
1437 // Make sure the dialog is big enough to be readable
1438 m_progDialog->Hide();
1439 wxSize sz = m_progDialog->GetSize();
1440 sz.x = csz.x * 9 / 10;
1441 m_progDialog->SetSize(sz);
1442
1443 m_progDialog->Layout();
1444 wxSize sza = m_progDialog->GetSize();
1445
1446 m_progDialog->Centre();
1447 m_progDialog->Show();
1448 m_progDialog->Raise();
1449
1450 m_skipout = false;
1451 m_skip = false;
1452 int yield = 0;
1453
1454 for (m_jcnt = 0; m_jcnt < ct_array.GetCount(); m_jcnt++) {
1455 wxString filename = ct_array[m_jcnt].chart_path;
1456 wxString CompressedCacheFilePath = CompressedCachePath(filename);
1457 double distance = ct_array[m_jcnt].distance;
1458
1459 ChartBase *pchart = ChartData->OpenChartFromDBAndLock(filename, FULL_INIT);
1460 if (!pchart) /* probably a corrupt chart */
1461 continue;
1462
1463 // bad things if more than one texfactory for a chart
1464 g_glTextureManager->PurgeChartTextures(pchart, true);
1465
1466 ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pchart);
1467 if (pBSBChart == 0) continue;
1468
1469 glTexFactory *tex_fact = new glTexFactory(pchart, g_raster_format);
1470
1471 m_progMsg.Printf(_("Distance from Ownship: %4.0f NMi"), distance);
1472 m_progMsg += "\n";
1473 m_progMsg.Prepend("Preparing RNC Cache...\n");
1474
1475 if (m_skipout) {
1476 g_glTextureManager->PurgeJobList();
1477 ChartData->DeleteCacheChart(pchart);
1478 delete tex_fact;
1479 break;
1480 }
1481
1482 int size_X = pBSBChart->GetSize_X();
1483 int size_Y = pBSBChart->GetSize_Y();
1484
1485 int tex_dim = g_GLOptions.m_iTextureDimension;
1486
1487 int nx_tex = ceil((float)size_X / tex_dim);
1488 int ny_tex = ceil((float)size_Y / tex_dim);
1489
1490 wxRect rect;
1491 rect.y = 0;
1492 rect.width = tex_dim;
1493 rect.height = tex_dim;
1494 for (int y = 0; y < ny_tex; y++) {
1495 rect.x = 0;
1496 for (int x = 0; x < nx_tex; x++) {
1497 for (int level = 0; level < g_mipmap_max_level + 1; level++) {
1498 if (!tex_fact->IsLevelInCache(level, rect, global_color_scheme)) {
1499 goto schedule;
1500 }
1501 }
1502 rect.x += rect.width;
1503 }
1504 rect.y += rect.height;
1505 }
1506 // Nothing to do
1507 // Free all possible memory
1508 ChartData->DeleteCacheChart(pchart);
1509 delete tex_fact;
1510 yield++;
1511 if (yield == 200) {
1512 ::wxYield();
1513 yield = 0;
1514 if (!m_progDialog->Update(m_jcnt)) {
1515 m_skip = true;
1516 m_skipout = true;
1517 }
1518 }
1519 continue;
1520
1521 // some work to do
1522 schedule:
1523
1524 yield = 0;
1525 ScheduleJob(tex_fact, wxRect(), 0, false, true, true, false);
1526 while (!m_skip) {
1527 ::wxYield();
1528 int cnt = GetJobCount() - GetRunningJobCount();
1529 if (!cnt) break;
1530 wxThread::Sleep(1);
1531 }
1532
1533 if (m_skipout) {
1534 g_glTextureManager->PurgeJobList();
1535 ChartData->DeleteCacheChart(pchart);
1536 delete tex_fact;
1537 break;
1538 }
1539 }
1540
1541 while (GetRunningJobCount()) {
1542 wxThread::Sleep(1);
1543 ::wxYield();
1544 }
1545
1546 b_inCompressAllCharts = false;
1547 m_timer.Start(500);
1548
1549 delete m_progDialog;
1550 m_progDialog = nullptr;
1551}
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.