From 12259324492d71cc6b90a5104d1eed3e46914547 Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Tue, 11 Aug 2026 13:33:25 -0300 Subject: [PATCH] [examples] Add core_scissor_shape (#6032) * [examples] Add core_scissor_shape Clip drawing to an arbitrary shape using the stencil buffer, since BeginScissorMode() only supports axis-aligned rectangles. * [examples] Rename to gl_scissor_shapes.c Rename core_scissor_shape to gl_scissor_shapes, move to others. Per @raysan5's review feedback on the PR: keep OpenGL-facing examples under examples/others, consistent with rlgl_standalone and raylib_opengl_interop, to avoid multi-platform/build-system issues. --- examples/others/gl_scissor_shapes.c | 153 ++++++++++++++++++++++++++ examples/others/gl_scissor_shapes.png | Bin 0 -> 16851 bytes 2 files changed, 153 insertions(+) create mode 100644 examples/others/gl_scissor_shapes.c create mode 100644 examples/others/gl_scissor_shapes.png diff --git a/examples/others/gl_scissor_shapes.c b/examples/others/gl_scissor_shapes.c new file mode 100644 index 000000000..0f2bef985 --- /dev/null +++ b/examples/others/gl_scissor_shapes.c @@ -0,0 +1,153 @@ +/******************************************************************************************* +* +* raylib [others] example - scissor shapes +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.0 +* +* Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2026 David Buzatto (@davidbuzatto) +* +********************************************************************************************/ + +#include "raylib.h" +#include "rlgl.h" + +#include // Required for: sinf() + +// BeginScissorMode() only clips to an axis-aligned rectangle, since it maps +// directly to glScissor. Clipping to an arbitrary shape (the same idea as +// Graphics2D.setClip(Shape) in Java2D) needs the stencil buffer instead: +// the clip shape is rasterized into the stencil buffer first, then further +// draws are only let through where the stencil was written. +// +// rlgl has no stencil wrapper (see raysan5/raylib discussion #2964), so this +// example calls the handful of OpenGL 1.0 stencil functions directly - they +// don't need glad/extension loading, same as glScissor doesn't. +#if defined(__APPLE__) + #include +#else + #if !defined(APIENTRY) + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include +#endif + +// mask is a callback instead of a fixed shape, so the clip region can be any +// combination of Draw* calls (circle, polygon, text, several shapes together) +typedef void (*ScissorMaskDrawFunc)(void *userData); + +static void BeginScissorModeShape(ScissorMaskDrawFunc mask, void *userData) +{ + rlDrawRenderBatchActive(); // Flush before touching stencil state + glClear(GL_STENCIL_BUFFER_BIT); + glEnable(GL_STENCIL_TEST); + glStencilMask(0xFF); + + // Pass 1: rasterize the mask into the stencil buffer only, no color write + rlColorMask(false, false, false, false); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); + mask(userData); + rlDrawRenderBatchActive(); // Flush the mask draw + + // Pass 2: subsequent draws only survive where the stencil was written + rlColorMask(true, true, true, true); + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); +} + +static void EndScissorModeShape(void) +{ + rlDrawRenderBatchActive(); + glDisable(GL_STENCIL_TEST); +} + +// Two example mask shapes, drawn in WHITE (the color is irrelevant here, +// only the stencil write from pass 1 matters) +typedef struct { Vector2 center; float radius; } CircleMask; +static void DrawCircleMask(void *userData) +{ + CircleMask *m = (CircleMask *)userData; + DrawCircleV(m->center, m->radius, WHITE); +} + +typedef struct { Vector2 center; float radius; int sides; } PolyMask; +static void DrawPolyMask(void *userData) +{ + PolyMask *m = (PolyMask *)userData; + DrawPoly(m->center, m->sides, m->radius, 0.0f, WHITE); +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [others] example - scissor shapes"); + + Vector2 circleCenter = { screenWidth/2.0f - 120, screenHeight/2.0f }; + Vector2 hexCenter = { screenWidth/2.0f + 120, screenHeight/2.0f }; + float maskRadius = 100.0f; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + circleCenter.y = screenHeight/2.0f + sinf((float)GetTime())*40.0f; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Circle-shaped clip: the full-screen rectangle only shows inside the circle + CircleMask circleMask = { circleCenter, maskRadius }; + BeginScissorModeShape(DrawCircleMask, &circleMask); + DrawRectangle(0, 0, screenWidth, screenHeight, BLUE); + EndScissorModeShape(); + + // Hexagon-shaped clip: same mechanism, a different mask shape + PolyMask hexMask = { hexCenter, maskRadius, 6 }; + BeginScissorModeShape(DrawPolyMask, &hexMask); + DrawRectangle(0, 0, screenWidth, screenHeight, ORANGE); + EndScissorModeShape(); + + DrawText("BeginScissorMode() only clips to a rectangle (glScissor)", 10, 10, 10, DARKGRAY); + DrawText("This clips to arbitrary shapes instead, using the stencil buffer", 10, 25, 10, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/others/gl_scissor_shapes.png b/examples/others/gl_scissor_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..485c5857d7b8dd8c31d48c5b6438467535f31078 GIT binary patch literal 16851 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ#=yWJp1k%11B2r%PZ!6Kin!!IzrMb% zZwoY#Vp^<-gDBx(Ty`-)j<@lmfCN^8v8wez278Lh+TOMU85})W2!oz72RGq^*d=?~ z4s76VyeK*0qKhpqXESlaGsqccx8x>{e2KKq|vv`{K(AUzgly z_P1U6(P`EH31{8qBafM0>N(AqlNR_$Zbqenal%8tp1(_4G)*tFY|L9~GOu8%XVE8~ zjhQ(HPb@LhT#i%25rOY)ndJxZ;2&HZu5 z!P92q?68iD<~Kgy$=GX*mdI?J8Xl=eY-r=%=O(w(v32c3&#s?4th{y{IoW$^Y08`W zgK6TXhx0U4yt9ro@0!0-aNd+zj;k$}PTlFOdcHTz1})z>+p|Qhc*>e(dQ7vFf8E6N z8s$sOvc1i!Z(a!Ued%H^O6N*Qbm+BMc!l@Iqhy0+Qy0#7?xwvSHP#v%xf#2rB`8|Y zz7ffuw8txBiuipFE4{rx&Mf)1!f0jQf@?htEB`U9Y&VtbyxOfluk&Thghy^ETc_-r zd_~7Cr|Q@ZE%xXF|qT^veFxY%JbYF&)O8R$mGw5rPCLRnSb&5z4+-R9up*oD)n~5X1?7 z#$D$cdfcv{8i$loPw+7=yU2p0Z~}+^7p}&Od?zxZq_GxHjTZ$LM00?$zwm9GmVnAH zUTj4dSd3@CfW%xzN#Cnr>#z|e+>DE?6&55r6(&x|E|gXFTKm{_;@O3*Gq>;+Iz2R= z(SJx*Cr|mh?3RN631-5xZ)b?MZ}E{f*!=OP;f&8`Gi=RJqocu=rDGpE%ilQ0r1Q^~ zAC>%f_+?zfMQgr}^Ro`yu5r7}lohqWwPHbe#034HEjR6?_!nP&itM)wW-KeJ1Qpkv zl8sj7|0K4pOYU}u+AgaJQZ;Ep$!t0iiCKwd0;|QRTh;vf*vfyx_QJy z9iBI%OCsV>gFbDCvRBlnRL4SVpleD{JJ?alyd}ZWp0Y;3=RsMY{b-g+Ary>!WD^w&-eoh z9%XNMuxa+34~knix_I44^cGvPW9|(D2R>)7$J{$V@+3btlD}DhJ}7MfALRAEq8*D{gETmhKb$UYa}#Su6V8HS&Wjt+?*LySt4FY%H43& z%1pIi`}WZFAB#(8T)LDObf{OR{J|yXwLcbJ`TM0SJZiar&?SY9b<0X`EeMZf`OLnJ zk8Q1$@#3%NkVCeojX{a!#Wlu*=9_%pax9!4%X~9pVe8!|Ez&FB9Wd+iTl+)ls!WpG zm&}Wb3)ABi)bby4v8U|4!gc##zz?CxY>w|#)MDJUC!=O~#!Jo(d9ngQQbjx5q8uL` zF=;uml{?LBr}Ckeruh?gp7_)vWxvF%r!&ysFIjrJT+&g;e=5i0auR#b%j6_>ubUU} z=kaIZbw3U*YZv-2v|4<2ec@97n1>B(=0zoXuakE8Hd*MHLbZl(_>q>=8B5X1KZhy~ zgSx-JzWxv3wW#Txbn!@oHA{wUp0Z{f+oko3U(Wv$)2DmJ?Qr3iEiy>ii}6<*!|99$ z{{I3CN>nOLdLQsHR_R@q>^**QiUn#?Hjr^j@HpSlqjnJ0&q&RQg`!M8iv`iL07`}@ zn8Opm&4VVKRwRluEjCtI+*yU1HW5jsAy3iZ7NewZ4z}D45tE&8(FDa+5MgM$>5o%F z30sOuXg`+r54aGypv&TBt+2Q=7nc(fwlEvbVnY;-Xsot)j-GQ3JA11OT1Q9>R$y&cwPaZ7h_3;v3l zXA#v2!(AtEINw0niO#ZCSe%JS80f4Ytp{H4oXA*(-dciZ4ti8J;BZdDS>J&&!!l5K z!ySbZB@ZE)VdZFMplOEy97&^@f#xkQa5#@a^r|8wF*Un)e=d)W~wPR#|+r9J%4u z76KmUk=`;Fx9$dH zRSWVVKD?8`!Lw07;%&tRwL@;!N{d}l#^ElggIlGVxh5=a9vnSoHZOz&7t2n(sFa8t zS{c${4~m#cF*RGTxLM0GOIt3UJMrQp6jdwa!Ky+`rI?xxSlq1Tnze&KHXen=F1Q^W zpwBYHjky?Xd_n7hjOWQ8mY%q1A*rj1+z@N1f;1Ipxj83HXgQFfT%MtF^y1A47nPF1 zI-!IHWY|u`M2e|dfyK>Q-dSC;@7UFhiKycn(5Aby6^onn7H9omZjW=eT#!U+9Wj1# zPAK8LmhnoGw~>*n@uJ(C4<1#Ey*IgaPe2Y+1zi@m=?T5dW?U4IXb_NiTf;Ly#awFo zV%HPU^b7Xk1Tk=DLo>~U<%I;(;@+zd=iYHkcXsPW>GmnefK{zbF=2Tj!nAm9|HJuL z+#XkKu|RRxAxJQIxj8o+Q#81xAv@DEr^GB}O9qOnHxN}uZq5yDiUzlSO6)wDZSu~& zdm6Ii56FR|XNsW|lL=SjMYb0im!y1|(b~EOkZ@Qf!OJMsavwQi zlDwChQnIA>!?E`ncY3CsU#w`b17e_(yK@7VqQR{VGQMs{+74tyN&0Fd^02ZkIDJ

(z>VJof8mc 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