#pragma once #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTHREAD #include #endif #include "common.h" #include "ThreadSafeQueue.h" #ifdef HAVE_LIBGLFW #ifndef GLFW_INCLUDE_NONE #define GLFW_INCLUDE_NONE #endif #include "glfw.h" #endif // GLM-independent utility functions (available even when HAVE_LIBGLM is undefined) // These are used by both OpenGL and Vulkan renderers but don't depend on GLM. namespace camp { // Runtime error handling (used by both OpenGL and Vulkan) inline void runtimeError(const std::string& s) { cerr << "error: " << s << endl; exit(-1); } template inline T ceilquotient(T a, T b) { return (a + b - 1) / b; } // Return the smallest power of 2 greater than or equal to n. inline unsigned int ceilpow2(unsigned int n) { --n; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return ++n; } // Utility functions for power-of-two checks (used by both OpenGL and Vulkan) inline bool ispow2(unsigned int n) {return n > 0 && !(n & (n - 1));} inline void checkpow2(unsigned int n, std::string s) { if(!ispow2(n)) { runtimeError(s+" must be a power of two"); exit(-1); } } /** * Detect NVIDIA RTX 3000-series (Ampere) GPUs which have a known driver bug * with fragment shader interlock causing rendering artifacts. * Checks for "RTX 30", "RTX30", or chip codename "GA10" in the device string. */ inline bool isNVIDIA30xx(const char* deviceStr) { if (!deviceStr) return false; string s(deviceStr); return s.find("NVIDIA") != string::npos && (s.find("RTX 30") != string::npos || s.find("RTX30") != string::npos || s.find("GA10") != string::npos); } } // namespace camp #ifdef HAVE_LIBGLM #include "glmCommon.h" #include "material.h" #include "pen.h" #include "triple.h" #include "seconds.h" #include "statistics.h" #include "render.h" namespace camp { class picture; class drawElement; #define EMPTY_VIEW 0, nullptr #define SINGLETON_VIEW(x) 1, &(x) #define VEC_VIEW(x) static_cast((x).size()), (x).data() #define STD_ARR_VIEW(x) static_cast((x).size()), (x).data() #define ARR_VIEW(x) static_cast(sizeof(x) / sizeof((x)[0])), x #define RAW_VIEW(x) static_cast(sizeof(x)), x #define ST_VIEW(s) static_cast(sizeof(s)), &s inline void store(float* f, double* C) { f[0] = C[0]; f[1] = C[1]; f[2] = C[2]; } inline void store(float* control, const triple& v) { control[0] = v.getx(); control[1] = v.gety(); control[2] = v.getz(); } inline void store(float* control, const triple& v, double weight) { control[0] = v.getx() * weight; control[1] = v.gety() * weight; control[2] = v.getz() * weight; control[3] = weight; } enum DrawMode: int { DRAWMODE_NORMAL, DRAWMODE_OUTLINE, DRAWMODE_WIREFRAME }; constexpr int NUM_DRAW_MODES = 3; // Verbosity threshold for timing partial sums (GPU indexing benchmarking) constexpr Int timePartialSumVerbosity = 4; struct Light { glm::vec4 direction; glm::vec4 color; }; struct Arcball { double angle; triple axis; Arcball(double x0, double y0, double x, double y) { triple v0 = norm(x0, y0); triple v1 = norm(x, y); double Dot = dot(v0, v1); angle = Dot > 1.0 ? 0.0 : Dot < -1.0 ? M_PI : acos(Dot); static triple Z(0.0, 0.0, 1.0); axis = unit(cross(v0, v1), Z); } triple norm(double x, double y) { double norm = hypot(x, y); if (norm > 1.0) { double denom = 1.0 / norm; x *= denom; y *= denom; } return triple(x, y, sqrt(max(1.0 - x * x - y * y, 0.0))); } }; struct projection { public: bool orthographic; camp::triple camera; camp::triple up; camp::triple target; double zoom; double angle; camp::pair viewportshift; projection(bool orthographic=false, camp::triple camera=0.0, camp::triple up=0.0, camp::triple target=0.0, double zoom=0.0, double angle=0.0, camp::pair viewportshift=0.0) : orthographic(orthographic), camera(camera), up(up), target(target), zoom(zoom), angle(angle), viewportshift(viewportshift) {} }; /** * RendererMessage - Message types for inter-thread communication. * Used to send commands from asymain thread to render thread. */ enum class RendererMessage { exportRender, // Request to export/render a frame updateRenderer, // Request to update renderer state createRenderer // Request the render thread to load and create the renderer }; #ifdef HAVE_PTHREAD /** * ThreadManager - Manages thread synchronization primitives. * * This class owns all pthread condition variables, mutexes, and the message * queue used for inter-thread communication between the asymain thread and * the render thread. It is independent of any specific rendering backend * (OpenGL, Vulkan, etc.). */ class ThreadManager { public: ThreadManager() = default; ~ThreadManager() = default; // Non-copyable, non-movable ThreadManager(const ThreadManager&) = delete; ThreadManager& operator=(const ThreadManager&) = delete; /// Main thread identifier (set by the loader when threading is enabled) pthread_t mainthread; /// Initialization signaling: render thread waits for asymain to finish setup pthread_cond_t initSignal = PTHREAD_COND_INITIALIZER; pthread_mutex_t initLock = PTHREAD_MUTEX_INITIALIZER; /// Export readiness signaling: asymain waits for render thread after export pthread_cond_t readySignal = PTHREAD_COND_INITIALIZER; pthread_mutex_t readyLock = PTHREAD_MUTEX_INITIALIZER; /// Message queue for inter-thread communication (asymain -> render thread) ThreadSafeQueue messageQueue; /// Signaling for renderer creation: asymain waits for render thread to finish creating pthread_cond_t createdSignal = PTHREAD_COND_INITIALIZER; pthread_mutex_t createdLock = PTHREAD_MUTEX_INITIALIZER; /** * Signal a condition variable and release its mutex. * Used to notify another thread that an operation has completed. */ void endwait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_mutex_unlock(&lock); } /** * Signal a condition variable and then wait on it. * Used for handshaking: signal the other thread, then block until * the other thread signals back. */ void wait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_cond_wait(&signal, &lock); pthread_mutex_unlock(&lock); } }; #endif // HAVE_PTHREAD /** * AsyRender - Library-agnostic base class for renderers. * Contains code that is independent of the underlying graphics API (Vulkan, OpenGL, etc.). */ class AsyRender: public gc { public: AsyRender() = default; virtual ~AsyRender() = default; /** Argument for render function - library-agnostic parameters */ struct RenderFunctionArgs: public gc { string prefix; picture const* pic; string format; double width; double height; double angle; double zoom; triple m; triple M; pair shift; pair margin; double* t; double* tup; double* background; size_t nlightsin; triple* lights; double* diffuse; bool view; int oldpid=0; }; /** Entry point for rendering. Called by the asymain thread to process * a render request. Derived classes must initialize their backend, copy * arguments via copyRenderArgs(), and either enter mainLoop() for interactive * viewing or produce output for export. */ virtual void render(RenderFunctionArgs const& args) = 0; /** Copy common arguments from RenderFunctionArgs to member variables. * Shared by all renderer implementations (Vulkan, OpenGL, WebGL). */ void copyRenderArgs(RenderFunctionArgs const& args); double getRenderResolution(triple Min) const; // Scene bounds double Xmin, Xmax; double Ymin, Ymax; double Zmin, Zmax; // Projection and camera state bool orthographic; glm::dmat4 rotateMat; glm::dmat4 projMat; glm::dmat4 viewMat; glm::dmat4 projViewMat; // Combined projection*view matrix for offscreen culling // Viewport dimensions int fullWidth, fullHeight; double X, Y; double Angle; double Zoom; double Zoom0; pair Shift; pair Margin; double ArcballFactor; // Lighting camp::triple* Lights; double* LightsDiffuse; size_t nlights; std::array Background; // Viewport/clip bounds double xmin, xmax; double ymin, ymax; // Materials std::vector materials; MaterialMap materialMap; bool Opaque; // Draw mode DrawMode mode = DRAWMODE_NORMAL; // Window/viewport management int screenWidth, screenHeight; double devicePixelRatio; int Width, Height; int oldWidth, oldHeight; double Aspect; double oWidth, oHeight; double lastzoom; int Fitscreen=1; double zoomFactor = 1.0; // Extra multiplicative zoom factor for fullscreen aspect compensation bool readyForExport=false; bool readyAfterExport=false; // Child process ID for export (used by both OpenGL and Vulkan) int Oldpid = 0; // GLFW window pointer (shared between Vulkan and OpenGL renderers) #ifdef HAVE_LIBGLFW GLFWwindow* glfwWindow = nullptr; #else void* glfwWindow = nullptr; #endif /** Returns the GLFW window pointer. Cast to GLFWwindow* when HAVE_LIBGLFW is defined. */ void* getGLFWWindow() const { return glfwWindow; } // Timer and statistics utils::stopWatch spinTimer; utils::stopWatch fpsTimer; utils::stopWatch frameTimer; utils::statistics fpsStats; size_t framecount = 0; // Rendering state flags bool redraw=false; bool redisplay=false; bool resize=false; bool remesh=true; bool antialias = false; bool ibl = false; bool queueExport=false; bool haveScene=false; bool waitEvent=true; // Window state (used by mainLoop) bool havewindow = false; // Initialization state (used by both OpenGL and Vulkan) bool initialized = false; bool copied = false; bool format3dWait = false; // SSBO support flag (true if shader storage buffers are available) // Vulkan always supports SSBOs, OpenGL may or may not - default to false for safety bool ssbo = false; // Will be set based on capability detection bool interlock = false; // Fragment shader interlock support // Renderer-specific initialization flags (used by both OpenGL and Vulkan) bool initSSBO = true; // Flag to indicate if SSBO buffers need initialization bool firstFit = true; // Flag for initial fit screen adjustment bool ViewExport = false; // Whether to export during view // Spin state static inline bool threads = false; // Window visibility bool hideWindow=false; // Spin state std::function currentIdleFunc = nullptr; bool Xspin = false; bool Yspin = false; bool Zspin = false; // Picture reference const picture* pic = nullptr; // Mouse/interaction state double xprev = 0.0; double yprev = 0.0; std::string lastAction = ""; // Window title std::string title = ""; // Internal state double H; double Xfactor, Yfactor; double cx, cy; // Transform matrices (used by camera()) double T[16]; double Tup[16]; // Normal matrices for shader transformations glm::dmat3 normMat; // Double precision normal matrix for CPU calculations /// Accessor for combined projection*view matrix (member version of getProjViewMat()) const glm::dmat4& getProjViewMat() const { return projViewMat; } /// Accessor for view matrix (member version of getViewMat()) const glm::dmat4& getViewMat() const { return viewMat; } /// Accessor for normal matrix (member version of getNormMat()) const glm::dmat3& getNormMat() const { return normMat; } #ifdef HAVE_PTHREAD // Thread synchronization manager (shared between renderers) ThreadManager threadMgr; #endif protected: const double pi=acos(-1.0); const double degrees=180.0/pi; const double radians=1.0/degrees; public: // String state (public for external access) string Format; bool View = false; string Prefix; protected: /** Set viewport dimensions and content offset. Called during initialization * and after resize. Derived classes may override to adjust backend-specific * state (e.g., Vulkan swapchain extents). */ virtual void setDimensions(int Width, int Height, double X, double Y); /** Update model-view uniform data for the current camera state. * Called before drawing when matrices have changed. */ virtual void updateModelViewData(); public: /** Compute and set the projection matrix from current orthographic/perspective * state, aspect ratio, and zoom. Derived classes may override to upload the * resulting matrix to a GPU uniform buffer. */ virtual void setProjection(); /** Update all transformation matrices (view, projection, combined proj*view). * Called each frame before drawing. Vulkan overrides to also set a dirty flag * for uniform buffer upload. */ virtual void update(); /** Set the projection matrix to a perspective frustum. * Derived classes may override for backend-specific handling. */ virtual void frustum(double left, double right, double bottom, double top, double nearVal, double farVal); /** Set the projection matrix to an orthographic frustum. * Derived classes may override for backend-specific handling. */ virtual void ortho(double left, double right, double bottom, double top, double nearVal, double farVal); /** Recompute the projection matrix and notify derived classes to upload. * Called after setProjection() or when viewport changes. */ void updateProjection(); // Clear functions (public for external access) void clearCenters(); void clearMaterials(); void clearData(); void prepareScene(); // Camera and view manipulation projection camera(bool user=true); void showCamera(); // User interaction handlers void shift(double dx, double dy); void pan(double dx, double dy); void capzoom(); void zoom(double dx, double dy); // Window size management void capsize(int& w, int& h); /// Given max framebuffer dimensions, return the largest size that fits /// while preserving the content Aspect ratio. void fitAspect(int& w, int& h); void windowposition(int& x, int& y, int width=-1, int height=-1); /** Set the window size and optionally reposition. Derived classes may override * to update backend-specific resources (e.g., Vulkan swapchain recreation). */ virtual void setsize(int w, int h, bool reposition=true); /** Toggle fullscreen mode. Derived classes may override for platform-specific * window management. */ virtual void fullscreen(bool reposition=true); /** Handle framebuffer reshape. Called when the window is resized. * Derived classes may override to update viewport or swapchain state. */ virtual void reshape(int width, int height); void setosize(); /** Fit the window to screen dimensions, preserving aspect ratio. * Derived classes may override for backend-specific behavior. */ virtual void fitscreen(bool reposition=true); /** Toggle fit-screen mode on/off. */ virtual void toggleFitScreen(); void initDisplay(int contentW, int contentH); void setOpaque(); /** Reset the camera to its default home position. */ virtual void home(); /** Cycle through draw modes (normal, outline, wireframe). * Derived classes may override to rebuild pipelines or shaders. */ virtual void cycleMode(); // Spin controls double spinStep(); void rotateX(double step); void rotateY(double step); void rotateZ(double step); void xspin(); void yspin(); void zspin(); void spinx(); void spiny(); void spinz(); // Idle function void idleFunc(std::function f); void idle(); // Window management (library-agnostic parts) /** Expand the window by increasing its dimensions. */ virtual void expand(); /** Shrink the window by decreasing its dimensions. */ virtual void shrink(); /** Handle an update request from the asymain thread. * Derived classes may override to refresh backend state (e.g., rebuild shaders). */ virtual void updateHandler(int=0); /** Handle an export request. The base class sets readyAfterExport; * derived classes override to perform backend-specific export setup. */ virtual void exportHandler(int); /** Export the current frame to an image or file. Pure virtual -- each * backend must implement its own pixel readback and encoding. */ virtual void Export(int imageIndex=0) = 0; // Message processing for inter-thread communication void processMessages(RendererMessage const& msg); /** Main display loop body. Calls prepareScene(), showWindow(), drawFrame(), * and swapBuffers() in sequence, with FPS tracking and message processing. * Derived classes typically do not override this; instead they override the * individual virtual hooks called by this method. */ virtual void display(); /** Swap front and back buffers. Called by display() after drawFrame(). * For Vulkan, presentation is handled inside drawFrame() via presentKHR, * so the default implementation is a no-op. OpenGL overrides to call * glfwSwapBuffers. */ virtual void swapBuffers(); /** Show the render window if it is hidden. Called by display() before * drawFrame(). Derived classes implement using GLFW show/hide calls. */ virtual void showWindow(); /** Render a single frame. Called by display() after prepareScene() and * showWindow(). The viewport is already set, scene data (materials, * triangles, etc.) is populated, and Width/Height reflect the current * framebuffer dimensions. Pure virtual -- each backend must implement. */ virtual void drawFrame() = 0; /** Request termination of the render loop. */ virtual void quit(); /** Handle window close request. Called by the GLFW close callback. * Derived classes may override for backend-specific cleanup. */ virtual void onClose(); /** Release all graphics library resources (buffers, shaders, pipelines). * Called during shutdown. Derived classes must implement backend-specific * resource destruction. */ virtual void finalizeProcess(); /** Handle key press/release events. Called by GLFW keyboard callback. * The base class implements shared key bindings (quit, fullscreen, mode * cycling, spin). Derived classes may override for additional handling. */ virtual void onKey(int key, int scancode, int action, int mods); /** Handle scroll wheel events. Called by GLFW scroll callback. * The base class maps scroll to zoom. Derived classes may override. */ virtual void onScroll(double xoffset, double yoffset); /** Handle mouse button press/release. Called by GLFW mouse button callback. * The base class tracks click position and action for pan/rotate. * Derived classes may override (e.g., Vulkan adds focus loss handling). */ virtual void onMouseButton(int button, int action, int mods); /** Handle cursor position changes. Called by GLFW cursor pos callback. * The base class computes delta from previous position for pan/rotate. * Derived classes may override for additional tracking. */ virtual void onCursorPos(double xpos, double ypos); /** Handle framebuffer resize. Called by GLFW framebuffer resize callback. * Derived classes typically override to update viewport and recreate * backend-specific resources (e.g., Vulkan swapchain/images). */ virtual void onFramebufferResize(int width, int height); // Main event loop (shared between OpenGL and Vulkan renderers) void mainLoop(); #ifdef HAVE_PTHREAD // Pthread synchronization helpers (forward to ThreadManager) void endwait(pthread_cond_t& signal, pthread_mutex_t& lock) { threadMgr.endwait(signal, lock); } void wait(pthread_cond_t& signal, pthread_mutex_t& lock) { threadMgr.wait(signal, lock); } #endif }; // Renderer pointer - unified interface for both OpenGL and Vulkan // This allows dynamic loading of the appropriate renderer at runtime extern AsyRender* gl; // Global renderer instance (type depends on build configuration) #ifdef HAVE_RENDERER #ifdef HAVE_GL class AsyGLRender; // Forward declaration #endif #ifdef HAVE_VULKAN class AsyVkRender; // Forward declaration (if Vulkan is available) #endif class NoRender; // Forward declaration for WebGL/v3d output /** * Lazily initialise the global renderer (Vulkan, OpenGL, or NoRender) on first use. * This defers all graphics-library loading until a shipout3 call actually * requires rendering, allowing headless modes like "-l" to run without * needing any GPU / display at all. * * @param format Output format string (e.g., "html", "v3d", or nullptr for default) */ #endif void initRenderer(const char* format); void mode(); } // namespace camp #endif // HAVE_LIBGLM