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