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