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