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