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