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 // bump to front
964 auto found = std::find(todo_list.begin(), todo_list.end(), ticket);
965 if (found != todo_list.end()) todo_list.erase(found);
966 todo_list.insert(todo_list.begin(), ticket);
967 ticket->level_min_request = level;
968 return false;
969 }
970 }
971
972 // avoid duplicate worker jobs
973 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
974 JobTicket *ticket = *node;
975 if (ticket->m_rect == rect && ticket->m_ChartPath == chart_path) {
976 return false;
977 }
978 }
979 }
980
981 JobTicket *pt = new JobTicket;
982 pt->pFact = client;
983 pt->m_rect = rect;
984 pt->level_min_request = level;
985 glTextureDescriptor *ptd = client->GetOrCreateTD(pt->m_rect);
986 pt->ident = (ptd->tex_name << 16) + level;
987 pt->b_throttle = b_throttle_thread;
988 pt->m_ChartPath = chart_path;
989
990 pt->level0_bits = NULL;
991 pt->b_abort = false;
992 pt->b_isaborted = false;
993 pt->bpost_zip_compress = b_postZip;
994 pt->binplace = b_inplace;
995 pt->b_inCompressAll = b_inCompressAllCharts;
996
997 /* do we compress in ram using builtin libraries, or do we
998 upload to the gpu and use the driver to perform compression?
999 we have builtin libraries for DXT1 (squish) and ETC1 (etcpak)
1000 FXT1 must use the driver, ETC1 cannot, and DXT1 can use the driver
1001 but the results are worse and don't compress well.
1002
1003 additionally, if we use the driver we must stay single threaded in this thread
1004 (unless we created multiple opengl contexts), but with with our own libraries,
1005 we can use multiple threads to take advantage of multiple cores */
1006
1007 if (g_raster_format != GL_COMPRESSED_RGB_FXT1_3DFX) {
1008 todo_list.insert(todo_list.begin(), pt); // push to front as a stack
1009 if (bthread_debug) {
1010 int mem_used;
1011 platform::GetMemoryStatus(0, &mem_used);
1012 printf("Adding job: %08X Job Count: %lu mem_used %d\n", pt->ident,
1013 (unsigned long)todo_list.size(), mem_used);
1014 }
1015
1016 StartTopJob();
1017 } else {
1018 // give level 0 buffer to the ticket
1019 pt->level0_bits = ptd->map_array[0];
1020 ptd->map_array[0] = NULL;
1021
1022 pt->DoJob();
1023
1024 OCPN_CompressionThreadEvent Nevent(wxEVT_OCPN_COMPRESSIONTHREAD, 0);
1025 Nevent.type = 0;
1026 Nevent.SetTicket(pt);
1027 ProcessEventLocally(Nevent);
1028 // from here m_ticket is undefined (if deleted in event handler)
1029 }
1030 return true;
1031}
1032
1033bool glTextureManager::StartTopJob() {
1034 auto node = todo_list.begin();
1035 if (node == todo_list.end()) return false;
1036
1037 JobTicket *ticket = *node;
1038
1039 // Is it possible to start another job?
1040 if (GetRunningJobCount() >= wxMax(m_max_jobs - ticket->b_throttle, 1))
1041 return false;
1042 auto found = std::find(todo_list.begin(), todo_list.end(), ticket);
1043 if (found != todo_list.end()) todo_list.erase(found);
1044
1045 glTextureDescriptor *ptd = ticket->pFact->GetpTD(ticket->m_rect);
1046 // don't need the job if we already have the compressed data
1047 if (ptd->comp_array[0]) {
1048 delete ticket;
1049 return StartTopJob();
1050 }
1051
1052 if (ptd->map_array[0]) {
1053 if (ticket->level_min_request == 0) {
1054 // give level 0 buffer to the ticket
1055 ticket->level0_bits = ptd->map_array[0];
1056 ptd->map_array[0] = NULL;
1057 } else {
1058 // would be nicer to use reference counters
1059 int size = TextureTileSize(0, false);
1060 ticket->level0_bits = (unsigned char *)malloc(size);
1061 memcpy(ticket->level0_bits, ptd->map_array[0], size);
1062 }
1063 }
1064
1065 running_list.push_back(ticket);
1066 DoThreadJob(ticket);
1067
1068 return true;
1069}
1070
1071bool glTextureManager::DoThreadJob(JobTicket *pticket) {
1072 if (bthread_debug)
1073 printf(" Starting job: %08X Jobs running: %d Jobs left: %lu\n",
1074 pticket->ident, GetRunningJobCount(),
1075 (unsigned long)todo_list.size());
1076
1079 CompressionPoolThread *t = new CompressionPoolThread(pticket, this);
1080 pticket->pthread = t;
1081
1082 t->Run();
1083
1084 return true;
1085}
1086
1087bool glTextureManager::AsJob(wxString const &chart_path) const {
1088 if (chart_path.Len()) {
1089 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
1090 JobTicket *ticket = *node;
1091 if (ticket->m_ChartPath.IsSameAs(chart_path)) {
1092 return true;
1093 }
1094 }
1095 }
1096 return false;
1097}
1098
1099void glTextureManager::PurgeJobList(wxString chart_path) {
1100 if (chart_path.Len()) {
1101 // Remove all pending jobs relating to the passed chart path
1102 auto &list = todo_list;
1103 auto removed_begin =
1104 std::remove_if(list.begin(), list.end(), [chart_path](JobTicket *t) {
1105 bool is_chart_match = t->m_ChartPath == chart_path;
1106 if (is_chart_match) delete t;
1107 return is_chart_match;
1108 });
1109 list.erase(removed_begin, list.end());
1110
1111 if (bthread_debug)
1112 std::cout << "Pool: Purge, todo count: " << list.size() << "\n";
1113 } else {
1114 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
1115 JobTicket *ticket = *node;
1116 delete ticket;
1117 }
1118 todo_list.clear();
1119 // Mark all running tasks for "abort"
1120 for (auto node = running_list.begin(); node != running_list.end(); ++node) {
1121 JobTicket *ticket = *node;
1122 ticket->b_abort = true;
1123 }
1124 }
1125}
1126
1127void glTextureManager::ClearJobList() {
1128 for (auto node = todo_list.begin(); node != todo_list.end(); ++node) {
1129 JobTicket *ticket = *node;
1130 delete ticket;
1131 }
1132 todo_list.clear();
1133}
1134
1135void glTextureManager::ClearAllRasterTextures() {
1136 // Delete all the TexFactory instances
1137 ChartPathHashTexfactType::iterator itt;
1138 for (itt = m_chart_texfactory_hash.begin();
1139 itt != m_chart_texfactory_hash.end(); ++itt) {
1140 glTexFactory *ptf = itt->second;
1141
1142 delete ptf;
1143 }
1144 m_chart_texfactory_hash.clear();
1145
1146 if (g_tex_mem_used != 0)
1147 wxLogMessage("Texture memory use calculation error\n");
1148}
1149
1150bool glTextureManager::PurgeChartTextures(ChartBase *pc, bool b_purge_factory) {
1151 // Look for the texture factory for this chart
1152 ChartPathHashTexfactType::iterator ittf =
1153 m_chart_texfactory_hash.find(pc->GetHashKey());
1154
1155 // Found ?
1156 if (ittf != m_chart_texfactory_hash.end()) {
1157 glTexFactory *pTexFact = ittf->second;
1158
1159 if (pTexFact) {
1160 if (b_purge_factory) {
1161 m_chart_texfactory_hash.erase(ittf); // This chart becoming invalid
1162
1163 delete pTexFact;
1164 }
1165
1166 return true;
1167 } else {
1168 m_chart_texfactory_hash.erase(ittf);
1169 return false;
1170 }
1171 } else
1172 return false;
1173}
1174
1175bool glTextureManager::TextureCrunch(double factor) {
1176 double hysteresis = 0.90;
1177
1178 bool bGLMemCrunch =
1179 g_tex_mem_used >
1180 (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) * factor;
1181 if (!bGLMemCrunch) return false;
1182
1183 ChartPathHashTexfactType::iterator it0;
1184 for (it0 = m_chart_texfactory_hash.begin();
1185 it0 != m_chart_texfactory_hash.end(); ++it0) {
1186 glTexFactory *ptf = it0->second;
1187 if (!ptf) continue;
1188 wxString chart_full_path = ptf->GetChartPath();
1189
1190 bGLMemCrunch = g_tex_mem_used >
1191 (double)(g_GLOptions.m_iTextureMemorySize * 1024 * 1024) *
1192 factor * hysteresis;
1193 if (!bGLMemCrunch) break;
1194
1195 // For each canvas
1196 for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1197 ChartCanvas *cc = g_canvasArray.Item(i);
1198 if (cc) {
1199 if (cc->GetVP().b_quilt) // quilted
1200 {
1201 if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1202 !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1203 ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1204 1024 * factor * hysteresis);
1205 }
1206 } else // not quilted
1207 {
1208 if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1209 ptf->DeleteSomeTextures(g_GLOptions.m_iTextureMemorySize * 1024 *
1210 1024 * factor * hysteresis);
1211 }
1212 }
1213 }
1214 }
1215 }
1216
1217 return true;
1218}
1219
1220#define MAX_CACHE_FACTORY 50
1221bool glTextureManager::FactoryCrunch(double factor) {
1222 if (m_chart_texfactory_hash.size() == 0) {
1223 /* nothing to free */
1224 return false;
1225 }
1226
1227 int mem_used;
1228 platform::GetMemoryStatus(0, &mem_used);
1229 double hysteresis = 0.90;
1230 ChartPathHashTexfactType::iterator it0;
1231
1232 bool bMemCrunch =
1233 (g_memCacheLimit &&
1234 ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1235 mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1236 (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1237
1238 if (!bMemCrunch) return false;
1239
1240 // Need more, so delete the oldest factory
1241 // Find the oldest unused factory
1242 int lru_oldest = 2147483647;
1243 glTexFactory *ptf_oldest = NULL;
1244
1245 for (it0 = m_chart_texfactory_hash.begin();
1246 it0 != m_chart_texfactory_hash.end(); ++it0) {
1247 glTexFactory *ptf = it0->second;
1248 if (!ptf) continue;
1249 wxString chart_full_path = ptf->GetChartPath();
1250
1251 // we better have to find one because glTexFactory keep cache texture open
1252 // and ocpn will eventually run out of file descriptors
1253
1254 // For each canvas
1255 for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1256 ChartCanvas *cc = g_canvasArray.Item(i);
1257 if (cc) {
1258 if (cc->GetVP().b_quilt) // quilted
1259 {
1260 if (cc->m_pQuilt && cc->m_pQuilt->IsComposed() &&
1261 !cc->m_pQuilt->IsChartInQuilt(chart_full_path)) {
1262 int lru = ptf->GetLRUTime();
1263 if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1264 lru_oldest = lru;
1265 ptf_oldest = ptf;
1266 }
1267 }
1268 } else {
1269 if (!cc->m_singleChart->GetFullPath().IsSameAs(chart_full_path)) {
1270 int lru = ptf->GetLRUTime();
1271 if (lru < lru_oldest && !ptf->BackgroundCompressionAsJob()) {
1272 lru_oldest = lru;
1273 ptf_oldest = ptf;
1274 }
1275 }
1276 }
1277 }
1278 }
1279 }
1280
1281 // Found one?
1282 if (!ptf_oldest) return false;
1283
1284 ptf_oldest->FreeSome(g_memCacheLimit * factor * hysteresis);
1285
1286 platform::GetMemoryStatus(0, &mem_used);
1287
1288 bMemCrunch = (g_memCacheLimit &&
1289 ((mem_used > (double)(g_memCacheLimit)*factor * hysteresis &&
1290 mem_used > (double)(m_prevMemUsed)*factor * hysteresis) ||
1291 (m_chart_texfactory_hash.size() > MAX_CACHE_FACTORY)));
1292
1293 if (!bMemCrunch) return false;
1294
1295 // Need more, so delete the oldest chart too
1296
1297 m_chart_texfactory_hash.erase(
1298 ptf_oldest->GetHashKey()); // This chart becoming invalid
1299
1300 delete ptf_oldest;
1301
1302 return true;
1303}
1304
1305void glTextureManager::BuildCompressedCache() {
1306 idx_sorted_by_distance.Clear();
1307
1308 // Building the cache may take a long time....
1309 // Be a little smarter.
1310 // Build a sorted array of chart database indices, sorted on distance from the
1311 // ownship currently. This way, a user may build a few charts textures for
1312 // immediate use, then "skip" out on the rest until later.
1313 int count = 0;
1314 for (int i = 0; i < ChartData->GetChartTableEntries(); i++) {
1315 /* skip if not kap */
1316 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1317 ChartTypeEnum chart_type = (ChartTypeEnum)cte.GetChartType();
1318 if (chart_type == CHART_TYPE_PLUGIN) {
1319 if (cte.GetChartFamily() != CHART_FAMILY_RASTER) continue;
1320 } else {
1321 if (chart_type != CHART_TYPE_KAP) continue;
1322 }
1323
1324 wxString CompressedCacheFilePath =
1325 CompressedCachePath(ChartData->GetDBChartFileName(i));
1326 wxFileName fn(CompressedCacheFilePath);
1327 // if(fn.FileExists()) /* skip if file exists */
1328 // continue;
1329
1330 idx_sorted_by_distance.Add(i);
1331
1332 count++;
1333 }
1334
1335 if (count == 0) return;
1336
1337 wxLogMessage(wxString::Format("BuildCompressedCache() count = %d", count));
1338
1339 m_timer.Stop();
1340 PurgeJobList();
1341 if (GetRunningJobCount()) {
1342 wxLogMessage("Starting compressor pool drain");
1343 wxDateTime now = wxDateTime::Now();
1344 time_t stall = now.GetTicks();
1345#define THREAD_WAIT_SECONDS 5
1346 time_t end = stall + THREAD_WAIT_SECONDS;
1347
1348 int n_comploop = 0;
1349 while (stall < end) {
1350 wxDateTime later = wxDateTime::Now();
1351 stall = later.GetTicks();
1352
1353 wxString msg;
1354 msg.Printf("Time: %d Job Count: %d", n_comploop, GetRunningJobCount());
1355 wxLogMessage(msg);
1356 if (!GetRunningJobCount()) break;
1357 wxYield();
1358 wxSleep(1);
1359 }
1360
1361 wxString fmsg;
1362 fmsg.Printf("Finished compressor pool drain..Time: %d Job Count: %d",
1363 n_comploop, GetRunningJobCount());
1364 wxLogMessage(fmsg);
1365 }
1366 ClearAllRasterTextures();
1367 b_inCompressAllCharts = true;
1368
1369 // Build another array of sorted compression targets.
1370 // We need to do this, as the chart table will not be invariant
1371 // after the compression threads start, so our index array will be invalid.
1372
1373 ArrayOfCompressTargets ct_array;
1374 for (unsigned int j = 0; j < idx_sorted_by_distance.GetCount(); j++) {
1375 int i = idx_sorted_by_distance[j];
1376
1377 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1378 double distance = chart_dist(i);
1379
1380 wxString filename = cte.GetFullSystemPath();
1381
1383 pct->distance = distance;
1384 pct->chart_path = filename;
1385
1386 ct_array.Add(pct);
1387 }
1388
1389 // create progress dialog
1390 long style = wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
1391 wxPD_REMAINING_TIME | wxPD_CAN_ABORT;
1392
1393 wxString msg0;
1394 msg0 =
1395 " "
1396 " \n \n ";
1397
1398#ifdef __WXQT__
1399 msg0 =
1400 "Very "
1401 "longgggggggggggggggggggggggggggggggggggggggggggg\ngggggggggggggggggg"
1402 "gggggggggggggggggggggggggg top line ";
1403#endif
1404
1405 for (int i = 0; i < m_max_jobs + 1; i++)
1406 msg0 += "\n ";
1407
1408 m_progDialog = new wxGenericProgressDialog();
1409
1410 wxFont *qFont = GetOCPNScaledFont(_("Dialog"));
1411 int fontSize = qFont->GetPointSize();
1412 wxFont *sFont;
1413 wxSize csz = gFrame->GetClientSize();
1414 if (csz.x < 500 || csz.y < 500)
1415 sFont = FontMgr::Get().FindOrCreateFont(
1416 10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1417 else
1418 sFont = FontMgr::Get().FindOrCreateFont(fontSize, wxFONTFAMILY_TELETYPE,
1419 wxFONTSTYLE_NORMAL,
1420 wxFONTWEIGHT_NORMAL);
1421
1422 m_progDialog->SetFont(*sFont);
1423
1424 // Should we use "compact" screen layout?
1425 wxScreenDC sdc;
1426 int height, width;
1427 sdc.GetTextExtent("[WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW]", &width, &height, NULL,
1428 NULL, sFont);
1429 if (width > (csz.x / 2)) m_bcompact = true;
1430
1431 m_progDialog->Create(_("OpenCPN Compressed Cache Update"), msg0, count + 1,
1432 NULL, style);
1433
1434 // Make sure the dialog is big enough to be readable
1435 m_progDialog->Hide();
1436 wxSize sz = m_progDialog->GetSize();
1437 sz.x = csz.x * 9 / 10;
1438 m_progDialog->SetSize(sz);
1439
1440 m_progDialog->Layout();
1441 wxSize sza = m_progDialog->GetSize();
1442
1443 m_progDialog->Centre();
1444 m_progDialog->Show();
1445 m_progDialog->Raise();
1446
1447 m_skipout = false;
1448 m_skip = false;
1449 int yield = 0;
1450
1451 for (m_jcnt = 0; m_jcnt < ct_array.GetCount(); m_jcnt++) {
1452 wxString filename = ct_array[m_jcnt].chart_path;
1453 wxString CompressedCacheFilePath = CompressedCachePath(filename);
1454 double distance = ct_array[m_jcnt].distance;
1455
1456 ChartBase *pchart = ChartData->OpenChartFromDBAndLock(filename, FULL_INIT);
1457 if (!pchart) /* probably a corrupt chart */
1458 continue;
1459
1460 // bad things if more than one texfactory for a chart
1461 g_glTextureManager->PurgeChartTextures(pchart, true);
1462
1463 ChartBaseBSB *pBSBChart = dynamic_cast<ChartBaseBSB *>(pchart);
1464 if (pBSBChart == 0) continue;
1465
1466 glTexFactory *tex_fact = new glTexFactory(pchart, g_raster_format);
1467
1468 m_progMsg.Printf(_("Distance from Ownship: %4.0f NMi"), distance);
1469 m_progMsg += "\n";
1470 m_progMsg.Prepend("Preparing RNC Cache...\n");
1471
1472 if (m_skipout) {
1473 g_glTextureManager->PurgeJobList();
1474 ChartData->DeleteCacheChart(pchart);
1475 delete tex_fact;
1476 break;
1477 }
1478
1479 int size_X = pBSBChart->GetSize_X();
1480 int size_Y = pBSBChart->GetSize_Y();
1481
1482 int tex_dim = g_GLOptions.m_iTextureDimension;
1483
1484 int nx_tex = ceil((float)size_X / tex_dim);
1485 int ny_tex = ceil((float)size_Y / tex_dim);
1486
1487 wxRect rect;
1488 rect.y = 0;
1489 rect.width = tex_dim;
1490 rect.height = tex_dim;
1491 for (int y = 0; y < ny_tex; y++) {
1492 rect.x = 0;
1493 for (int x = 0; x < nx_tex; x++) {
1494 for (int level = 0; level < g_mipmap_max_level + 1; level++) {
1495 if (!tex_fact->IsLevelInCache(level, rect, global_color_scheme)) {
1496 goto schedule;
1497 }
1498 }
1499 rect.x += rect.width;
1500 }
1501 rect.y += rect.height;
1502 }
1503 // Nothing to do
1504 // Free all possible memory
1505 ChartData->DeleteCacheChart(pchart);
1506 delete tex_fact;
1507 yield++;
1508 if (yield == 200) {
1509 ::wxYield();
1510 yield = 0;
1511 if (!m_progDialog->Update(m_jcnt)) {
1512 m_skip = true;
1513 m_skipout = true;
1514 }
1515 }
1516 continue;
1517
1518 // some work to do
1519 schedule:
1520
1521 yield = 0;
1522 ScheduleJob(tex_fact, wxRect(), 0, false, true, true, false);
1523 while (!m_skip) {
1524 ::wxYield();
1525 int cnt = GetJobCount() - GetRunningJobCount();
1526 if (!cnt) break;
1527 wxThread::Sleep(1);
1528 }
1529
1530 if (m_skipout) {
1531 g_glTextureManager->PurgeJobList();
1532 ChartData->DeleteCacheChart(pchart);
1533 delete tex_fact;
1534 break;
1535 }
1536 }
1537
1538 while (GetRunningJobCount()) {
1539 wxThread::Sleep(1);
1540 ::wxYield();
1541 }
1542
1543 b_inCompressAllCharts = false;
1544 m_timer.Start(500);
1545
1546 delete m_progDialog;
1547 m_progDialog = nullptr;
1548}
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:85
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:85
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.