Skip to content

Commit ddc03c6

Browse files
committed
Engine: AAA-style input arrays, font atlas cache, vsync, persist-FBO fixes for noLoop/refresh callback
1 parent 10057a2 commit ddc03c6

2 files changed

Lines changed: 149 additions & 17 deletions

File tree

src/Processing.cpp

Lines changed: 145 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,6 @@ static float _colorMaxA(){
292292

293293
color::color(int gray) { value = _makeColor((float)gray, _colorMaxA()).value; }
294294
color::color(int gray, int a) { value = _makeColor((float)gray, (float)a).value; }
295-
color::color(int r, int g, int b) { value = _makeColor((float)r,(float)g,(float)b,_colorMaxA()).value; }
296-
color::color(int r,int g,int b,int a){ value = _makeColor((float)r,(float)g,(float)b,(float)a).value; }
297295
color::color(float gray) { value = _makeColor(gray, _colorMaxA()).value; }
298296
color::color(float gray, float a) { value = _makeColor(gray, a).value; }
299297
color::color(float r,float g,float b){ value = _makeColor(r,g,b,_colorMaxA()).value; }
@@ -579,6 +577,7 @@ void PApplet::fullScreen() {
579577
}
580578
}
581579
void PApplet::frameRate(int fps){currentFrameRate=fps;targetFrameTime=1.0/fps;}
580+
void PApplet::vsync(bool on){if(gWindow) glfwSwapInterval(on?1:0);}
582581
void PApplet::noLoop(){looping=false;}
583582
void PApplet::loop() {looping=true;}
584583
void PApplet::redraw(){redrawOnce=true;}
@@ -722,20 +721,24 @@ void PApplet::setBg(float r,float g,float b,float a){
722721
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
723722
}
724723
void PApplet::background(float gray, float a) {
724+
_backgroundCalledThisFrame = true;
725725
color c = makeColor(gray, a);
726726
unsigned int v = c.value;
727727
setBg((v>>16&0xFF)/255.f, (v>>8&0xFF)/255.f, (v&0xFF)/255.f, (v>>24&0xFF)/255.f);
728728
}
729729
void PApplet::background(float r, float g, float b, float a) {
730+
_backgroundCalledThisFrame = true;
730731
color c = makeColor(r, g, b, a);
731732
unsigned int v = c.value;
732733
setBg((v>>16&0xFF)/255.f, (v>>8&0xFF)/255.f, (v&0xFF)/255.f, (v>>24&0xFF)/255.f);
733734
}
734735
void PApplet::background(color c) {
736+
_backgroundCalledThisFrame = true;
735737
unsigned int v = c.value;
736738
setBg((v>>16&0xFF)/255.f, (v>>8&0xFF)/255.f, (v&0xFF)/255.f, (v>>24&0xFF)/255.f);
737739
}
738740
void PApplet::background(const PImage& img) {
741+
_backgroundCalledThisFrame = true;
739742
// Draw image as full-canvas background
740743
if (img.width == 0 || img.height == 0) return;
741744
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -1900,19 +1903,28 @@ bool PApplet::loadTTFFile(const std::string& path) {
19001903

19011904
void PApplet::bakeAtlas(float pixelSize) {
19021905
if (!g_ttf.loaded) return;
1903-
if (std::fabs(pixelSize - g_ttf.bakeSize) < 0.5f) return;
1904-
g_ttf.bakeSize = pixelSize;
1905-
// Use a large atlas to guarantee all glyphs fit at any size
1906+
// Cache key: rounded to nearest 0.5px to avoid float noise
1907+
int cacheKey = (int)std::round(pixelSize * 2.0f);
1908+
// Check cache — if already baked this size, just activate it
1909+
auto it = g_ttf.atlasCache.find(cacheKey);
1910+
if (it != g_ttf.atlasCache.end()) {
1911+
g_ttf.current = &it->second;
1912+
g_ttf.texID = g_ttf.current->texID;
1913+
g_ttf.chars = g_ttf.current->chars;
1914+
g_ttf.bakeSize = pixelSize;
1915+
return;
1916+
}
1917+
// Not cached — bake a new atlas for this size
1918+
TTFAtlas atlas;
19061919
g_ttf.atlasW = 1024; g_ttf.atlasH = 1024;
19071920
std::vector<unsigned char> bitmap(g_ttf.atlasW * g_ttf.atlasH);
19081921
int ret = stbtt_BakeFontBitmap(
19091922
g_ttf.data.data(), 0, pixelSize,
19101923
bitmap.data(), g_ttf.atlasW, g_ttf.atlasH,
1911-
32, 96, g_ttf.chars);
1924+
32, 96, atlas.chars);
19121925
if (ret == 0) { g_ttf.loaded = false; return; }
1913-
// Upload as RGBA texture
1914-
if (g_ttf.texID == 0) glGenTextures(1, &g_ttf.texID);
1915-
glBindTexture(GL_TEXTURE_2D, g_ttf.texID);
1926+
glGenTextures(1, &atlas.texID);
1927+
glBindTexture(GL_TEXTURE_2D, atlas.texID);
19161928
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
19171929
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
19181930
std::vector<unsigned char> rgba(g_ttf.atlasW * g_ttf.atlasH * 4);
@@ -1923,6 +1935,12 @@ void PApplet::bakeAtlas(float pixelSize) {
19231935
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_ttf.atlasW, g_ttf.atlasH,
19241936
0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data());
19251937
glBindTexture(GL_TEXTURE_2D, 0);
1938+
// Store in cache
1939+
g_ttf.atlasCache[cacheKey] = std::move(atlas);
1940+
g_ttf.current = &g_ttf.atlasCache[cacheKey];
1941+
g_ttf.texID = g_ttf.current->texID;
1942+
g_ttf.chars = g_ttf.current->chars;
1943+
g_ttf.bakeSize = pixelSize;
19261944
}
19271945
#endif // PROCESSING_HAS_STB_TRUETYPE
19281946

@@ -2563,17 +2581,29 @@ static void mouse_btn_cb(GLFWwindow*, int btn, int action, int mods) {
25632581
p->g_currentMods = mods;
25642582
if (action == GLFW_PRESS) {
25652583
p->_mousePressed = true;
2584+
if(btn>=0&&btn<8) p->mouseButtons[btn]=true;
2585+
{ int mb=(btn==GLFW_MOUSE_BUTTON_LEFT)?37:(btn==GLFW_MOUSE_BUTTON_RIGHT)?39:3;
2586+
if(mb<128) p->mouseDown[mb]=true; }
25662587
if (btn == GLFW_MOUSE_BUTTON_LEFT) p->mouseButton = LEFT;
25672588
else if (btn == GLFW_MOUSE_BUTTON_RIGHT) p->mouseButton = RIGHT;
25682589
else p->mouseButton = CENTER;
2590+
p->_eventDrewSomething=true;
2591+
p->_eventDrewSomething=true;
25692592
p->mousePressed();
25702593
p->mouseWasPressed = true;
25712594
} else if (action == GLFW_RELEASE) {
2572-
p->_mousePressed = false;
2595+
if(btn>=0&&btn<8) p->mouseButtons[btn]=false;
2596+
{ int mb=(btn==GLFW_MOUSE_BUTTON_LEFT)?37:(btn==GLFW_MOUSE_BUTTON_RIGHT)?39:3;
2597+
if(mb<128) p->mouseDown[mb]=false; }
2598+
bool anyMouse=false;
2599+
for(int i=0;i<8;i++) if(p->mouseButtons[i]){anyMouse=true;break;}
2600+
p->_mousePressed=anyMouse;
2601+
p->mouseButton = p->mouseButtons[GLFW_MOUSE_BUTTON_LEFT] ? LEFT :
2602+
p->mouseButtons[GLFW_MOUSE_BUTTON_RIGHT] ? RIGHT :
2603+
p->mouseButtons[GLFW_MOUSE_BUTTON_MIDDLE] ? CENTER : -1;
25732604
p->mouseReleased();
2574-
if (p->mouseWasPressed && p->_onMouseClicked) p->_onMouseClicked();
2575-
p->mouseWasPressed = false;
2576-
p->mouseButton = -1;
2605+
if(p->mouseWasPressed) p->mouseClicked();
2606+
p->mouseWasPressed=false;
25772607
}
25782608
}
25792609
static void scroll_cb(GLFWwindow*,double,double yoffset){
@@ -2591,6 +2621,7 @@ static void char_cb(GLFWwindow*, unsigned int codepoint) {
25912621
// Fire deferred keyPressed() now that p->key has the correct char value
25922622
if (p->g_pendingKeyPressed) {
25932623
p->g_pendingKeyPressed = false;
2624+
p->_eventDrewSomething=true;
25942625
p->keyPressed();
25952626
}
25962627
// keyTyped() -- Processing standard: only printable chars, no action keys
@@ -2645,11 +2676,62 @@ static int glfw_to_java_keycode(int k) {
26452676
}
26462677

26472678
// Current modifier state -- set from key_cb mods parameter (reliable on Windows)
2679+
2680+
// Map GLFW keycode -> Processing/Java keyCode constant
2681+
static int glfw_to_processing_keycode(int k) {
2682+
if(k >= GLFW_KEY_A && k <= GLFW_KEY_Z) return 'A' + (k - GLFW_KEY_A); // 65-90
2683+
if(k >= GLFW_KEY_0 && k <= GLFW_KEY_9) return '0' + (k - GLFW_KEY_0); // 48-57
2684+
switch(k) {
2685+
case GLFW_KEY_UP: return 38;
2686+
case GLFW_KEY_DOWN: return 40;
2687+
case GLFW_KEY_LEFT: return 37;
2688+
case GLFW_KEY_RIGHT: return 39;
2689+
case GLFW_KEY_LEFT_SHIFT:
2690+
case GLFW_KEY_RIGHT_SHIFT: return 16; // SHIFT
2691+
case GLFW_KEY_LEFT_CONTROL:
2692+
case GLFW_KEY_RIGHT_CONTROL:return 17; // CONTROL
2693+
case GLFW_KEY_LEFT_ALT:
2694+
case GLFW_KEY_RIGHT_ALT: return 18; // ALT
2695+
case GLFW_KEY_SPACE: return 32;
2696+
case GLFW_KEY_ENTER:
2697+
case GLFW_KEY_KP_ENTER: return 10;
2698+
case GLFW_KEY_BACKSPACE: return 8;
2699+
case GLFW_KEY_TAB: return 9;
2700+
case GLFW_KEY_ESCAPE: return 27;
2701+
case GLFW_KEY_DELETE: return 127;
2702+
case GLFW_KEY_HOME: return 36;
2703+
case GLFW_KEY_END: return 35;
2704+
case GLFW_KEY_PAGE_UP: return 33;
2705+
case GLFW_KEY_PAGE_DOWN: return 34;
2706+
case GLFW_KEY_INSERT: return 155;
2707+
case GLFW_KEY_F1: return 112;
2708+
case GLFW_KEY_F2: return 113;
2709+
case GLFW_KEY_F3: return 114;
2710+
case GLFW_KEY_F4: return 115;
2711+
case GLFW_KEY_F5: return 116;
2712+
case GLFW_KEY_F6: return 117;
2713+
case GLFW_KEY_F7: return 118;
2714+
case GLFW_KEY_F8: return 119;
2715+
case GLFW_KEY_F9: return 120;
2716+
case GLFW_KEY_F10: return 121;
2717+
case GLFW_KEY_F11: return 122;
2718+
case GLFW_KEY_F12: return 123;
2719+
default: return -1;
2720+
}
2721+
}
2722+
26482723
static void key_cb(GLFWwindow* w, int k, int /*scancode*/, int action, int mods) {
26492724
auto* p = PApplet::g_papplet; if(!p) return;
26502725
p->g_currentMods = mods; // capture before callbacks fire
26512726
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
26522727
p->_keyPressed = true;
2728+
if(k>=0&&k<349) p->keys[k]=true;
2729+
{ int pk=glfw_to_processing_keycode(k); if(pk>=0&&pk<256) {
2730+
p->keysDown[pk]=true;
2731+
// Also store lowercase so K['w'] and K['W'] both work
2732+
if(pk>='A'&&pk<='Z') p->keysDown[pk+32]=true;
2733+
if(pk>='a'&&pk<='z') p->keysDown[pk-32]=true;
2734+
} }
26532735
p->g_pendingKeyPressed = false; // reset for each new p->key event
26542736

26552737
// Translate GLFW code -> Java KeyEvent.VK_* value (Processing reference standard)
@@ -2703,14 +2785,23 @@ static void key_cb(GLFWwindow* w, int k, int /*scancode*/, int action, int mods)
27032785

27042786
// Fire keyPressed() now unless deferred to char_cb
27052787
if (!p->g_pendingKeyPressed) {
2788+
if(action==GLFW_PRESS) p->_eventDrewSomething=true;
27062789
p->keyPressed();
27072790
// Java Processing: ESC closes the sketch unless keyPressed() set p->key=0
27082791
if (p->key == (char)27 && p->gWindow)
27092792
glfwSetWindowShouldClose(p->gWindow, GLFW_TRUE);
27102793
}
27112794

27122795
} else if (action == GLFW_RELEASE) {
2713-
p->_keyPressed = false;
2796+
if(k>=0&&k<349) p->keys[k]=false;
2797+
{ int pk=glfw_to_processing_keycode(k); if(pk>=0&&pk<256) {
2798+
p->keysDown[pk]=false;
2799+
if(pk>='A'&&pk<='Z') p->keysDown[pk+32]=false;
2800+
if(pk>='a'&&pk<='z') p->keysDown[pk-32]=false;
2801+
} }
2802+
// check if any key still held
2803+
bool anyHeld=false; for(int i=0;i<349;i++) if(p->keys[i]){anyHeld=true;break;}
2804+
p->_keyPressed=anyHeld;
27142805
p->g_currentMods = mods; // update on release too
27152806
p->keyReleased();
27162807
}
@@ -2799,7 +2890,25 @@ void PApplet::run(){
27992890
gWindow=glfwCreateWindow(winWidth,winHeight,g_sketchName.c_str(),nullptr,nullptr);
28002891
// Prevent freeze when dragging title bar on Windows
28012892
glfwSetWindowRefreshCallback(gWindow,[](GLFWwindow* w){
2802-
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
2893+
// Originally just glClear()+swap "to prevent freeze when dragging the
2894+
// title bar on Windows" -- but this unconditionally WIPES the screen
2895+
// on every OS/WM-triggered refresh event (resize, focus change,
2896+
// workspace switch, tiling re-layout, etc.), with no attempt to
2897+
// redraw actual sketch content. On tiling WMs like i3 that send
2898+
// refresh events frequently, this is what made noLoop() sketches
2899+
// (which never swap again after their one real draw) appear to
2900+
// "show the image, then go blank a few frames later" -- the WM's
2901+
// very next refresh event was clearing it out from under them.
2902+
// Restoring from the persist FBO (the last frame we know is
2903+
// correct) instead of blank-clearing keeps the window responsive
2904+
// during the same blocking scenarios (e.g. title-bar drag) while
2905+
// never destroying real content.
2906+
auto* p = PApplet::g_papplet;
2907+
if (p && p->persistFBO) {
2908+
p->restoreFromPersist();
2909+
} else {
2910+
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
2911+
}
28032912
glfwSwapBuffers(w);
28042913
});
28052914
if(!gWindow){
@@ -2811,6 +2920,7 @@ void PApplet::run(){
28112920
glfwTerminate(); return;
28122921
}
28132922
glfwMakeContextCurrent(gWindow);
2923+
glfwSwapInterval(1); // vsync on by default
28142924
GLenum glewErr = glewInit();
28152925
if(glewErr != GLEW_OK){
28162926
#ifdef _WIN32
@@ -2966,6 +3076,19 @@ void PApplet::run(){
29663076
flushPoints(); // flush any pending points before swap
29673077
saveToPersist(); // save back buffer before swap for next frame restore
29683078
glfwSwapBuffers(gWindow);
3079+
// For noLoop() sketches, this is the ONLY draw that will ever happen --
3080+
// the main loop below never swaps again (looping||redrawOnce stays
3081+
// false forever after this point). With double buffering, a single
3082+
// swap leaves the correct image in only ONE of the two buffers; the
3083+
// other buffer still holds stale content from the settle loop above.
3084+
// If anything outside our control (window manager/compositor on
3085+
// tiling WMs like i3, focus changes, etc.) forces an implicit buffer
3086+
// flip later, that stale buffer becomes visible and the sketch
3087+
// appears to "go blank" after a few frames. Restoring from the
3088+
// persist FBO we just saved and swapping again primes BOTH buffers
3089+
// with the correct content, so no swap-parity assumption is needed.
3090+
restoreFromPersist();
3091+
glfwSwapBuffers(gWindow);
29693092
}
29703093

29713094
redrawOnce = looping;
@@ -3022,7 +3145,7 @@ void PApplet::run(){
30223145
// Restore previous frame content from persist FBO.
30233146
// Sketches that call background() will overwrite this.
30243147
// Sketches that don't (like Continuous Lines) preserve their drawn content.
3025-
restoreFromPersist();
3148+
if(!_backgroundCalledThisFrame) restoreFromPersist();
30263149
glClear(GL_DEPTH_BUFFER_BIT);
30273150
}
30283151

@@ -3075,14 +3198,19 @@ void PApplet::run(){
30753198
// Reset tint state each frame (Processing Java behavior)
30763199
doTint=false; tintR=1; tintG=1; tintB=1; tintA=1;
30773200
glGetError();
3201+
_backgroundCalledThisFrame = false;
30783202
++frameCount; this->draw(); pmouseX=mouseX; pmouseY=mouseY; mouseDX=0; mouseDY=0;
30793203
glGetError(); // consume any GL errors
30803204
}
30813205

3082-
saveToPersist();
3206+
// Only save persist FBO if sketch uses persistence (no background() call)
3207+
if(!_backgroundCalledThisFrame) saveToPersist();
30833208
glfwSwapBuffers(gWindow);
30843209
}
30853210
glfwPollEvents();
3211+
// Only re-save persist if an event callback drew something.
3212+
// Checked via _eventDrewSomething flag set in keyPressed/mousePressed.
3213+
if(persistFBO && _eventDrewSomething) { saveToPersist(); _eventDrewSomething=false; }
30863214
auto now=std::chrono::steady_clock::now();
30873215
double elapsed=std::chrono::duration<double>(now-last).count();
30883216
if(elapsed>0) measuredFrameRate=measuredFrameRate*0.9f+(float)(1.0/elapsed)*0.1f;

src/Processing_api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ inline float sq(float x) { return PApplet::sq(x); }
1111
inline float lerp(float a,float b,float t) { return PApplet::lerp(a,b,t); }
1212
inline float map(float v,float i0,float i1,float o0,float o1) { return PApplet::map(v,i0,i1,o0,o1); }
1313
inline float constrain(float v,float lo,float hi) { return PApplet::constrain(v,lo,hi); }
14+
inline float max(float a,float b) { return PApplet::max(a,b); }
15+
inline float min(float a,float b) { return PApplet::min(a,b); }
16+
inline float max(float a,float b,float c) { return PApplet::max(a,b,c); }
17+
inline float min(float a,float b,float c) { return PApplet::min(a,b,c); }
1418
inline float dist(float x1,float y1,float x2,float y2) { return PApplet::dist(x1,y1,x2,y2); }
1519
inline float dist(float x1,float y1,float z1,float x2,float y2,float z2) { return PApplet::dist(x1,y1,z1,x2,y2,z2); }
1620
inline float mag(float x,float y) { return PApplet::mag(x,y); }

0 commit comments

Comments
 (0)