GoCL follows one rule: detect capability first, decide later.
At startup, the engine snapshots the GPU and driver features available on the active device. Rendering decisions, shader adjustments, and texture format choices are derived from that snapshot instead of hard‑coded vendor paths.
The same codebase powers two use‑cases:
GoCL_core.a into your own Vulkan application to get cross‑generational rendering, texture compression, and performance utilities out of the box.vulkan_proxy.so (or .dll)) that registers via VK_LAYER_GoCL_Proxy.json and chains through the loader’s dispatch tables. It also works as a classic LD_PRELOAD / vulkan‑1.dll shim for environments without layer support.CapabilityOracle)GoCL queries the physical device for every feature that matters for cross‑generational rendering:
VK_EXT_memory_budget or fallback)These results are stored in DeviceCapabilities and fed to every subsystem.
LegacyShaderEmulator)Shaders that use features the current hardware cannot support are rewritten before the driver ever sees them. Transformations include:
This runs once at pipeline creation — it adds zero per‑frame cost.
Engine‑side
When the device supports VK_EXT_device_generated_commands, the engine creates a fixed token layout (PUSH_CONSTANT + DRAW_INDEXED) and an execution set. Pipelines are marked with INDIRECT_BINDABLE_BIT. Indirect draw calls are replaced by a single vkCmdExecuteGeneratedCommandsEXT, shifting CPU work to the GPU.
Proxy‑side
The proxy intercepts vkCmdDrawIndirect* and vkCmdBindPipeline, tracks the current pipeline per command buffer, and records indirect data into a persistent input buffer via vkCmdCopyBuffer. At vkEndCommandBuffer, it executes all accumulated sequences. If DGC is unsupported or disabled, the proxy falls back to forwarding the original indirect calls.
Fallback
When DGC is not available, the engine/proxy transparently falls back to standard CPU‑driven indirect draws. The disable_dgc config flag forces fallback for debugging.
TextureManager + ASTCBlockSelector)Textures are loaded, analysed, and compressed according to the GPU’s actual capabilities:
VK_EXT_astc_decode_mode)VK_EXT_texture_compression_astc_hdr is presentBlock sizes are chosen dynamically based on texture usage, VRAM pressure, and a hardware‑empathy hint (isMobileTier / isLowBandwidth). All thresholds can be overridden in GoCL.conf.
For pre‑compressed .basis assets, the engine can transcode directly to ETC2 via TranscodeBasisToETC2, bypassing the heavyweight UASTC compression step entirely. This path is ideal for applications that ship their own offline‑compressed basis files.
The ETC2 transcoder is initialised once with the required global selector codebook, ensuring correct decode quality as recommended by the Basis Universal specification. All transcode paths share this static transcoder.
DynamicResolutionHelper)Renders the scene to a downscaled off‑screen target, then upscales with a high‑quality spatial filter. Quality presets and the scale factor are controlled via GoCL.conf.
MeshletCuller)Provides GPU‑driven frustum culling using a compute shader, replacing mesh shaders on hardware that lacks VK_EXT_mesh_shader. Batching limits can be tuned in GoCL.conf.
Both utilities are available only when linking against the GoCL library. They are not injected by the proxy.
GoCL handles:
CommandPool::initCompute)Pipeline cache data is persisted to disk and reused across runs, dramatically reducing shader recompilation hitches on older drivers and slower CPUs.
The cache file path can be configured via pipeline_cache_path = "gocl_cache.bin" in the [go_context] section of GoCL.conf. The path is resolved relative to the game’s working directory (or absolute if provided). The proxy loads from this file when the application provides no initial cache, and saves the final cache on vkDestroyPipelineCache.
src/layer/Proxy.cpp)The proxy is a Vulkan implicit layer that negotiates with the loader via VkLayerInstanceCreateInfo / VkLayerDeviceCreateInfo. It intercepts key entry points and applies all of GoCL’s cross‑generational optimisations.
Activation
ENABLE_GOCL_LAYER=1 and point VK_LAYER_PATH to the directory containing VkLayer_GoCL_Proxy.json and vulkan_proxy.so (or .dll).LD_PRELOAD shim or vulkan‑1.dll proxy.vkCreateInstance / vkDestroyInstance – instance lifecycle and GPA chainvkCreateDevice / vkDestroyDevice – device capability snapshot and GDPA chainvkGetDeviceProcAddr – replaces critical device‑level function pointersvkCreateShaderModule – patches SPIR‑V before compilationvkCreateGraphicsPipelines / vkCreateComputePipelines – passthroughvkCreateSwapchainKHR – injects image count and present mode from GoCL.confvkCreateImage / vkDestroyImage – replaces ASTC formats with ETC2 when hardware decode is absentvkCmdCopyBufferToImage – on‑the‑fly ASTC → ETC2vkAllocateCommandBuffers, vkBindBufferMemory, vkDestroyBuffer – track buffer‑memory associations for the transcode pathWhen the target GPU lacks hardware ASTC decode, the proxy:
astcencTranscodeToETC2) – this is necessary because the proxy only has access to the decoded RGBA dataThe heavy UASTC encoder is kept in the separate gocl_transcoder.so (or .dll) and loaded lazily, so it only affects games that actually use ASTC textures.
The proxy tracks the available VRAM budget using VK_EXT_memory_budget (fallback to static heap size when the extension is unavailable). When memory pressure is detected, it automatically adjusts swapchain behaviour:
minImageCount is clamped to the surface’s minimum (typically 2 instead of 3), reducing the number of swapchain images.These thresholds are currently fixed. If DRS is not enabled, only the swapchain image count reduction applies. The VRAM budget query itself is always active when the extension is available, but no action is taken when memory is sufficient.
Note on
max_frames_in_flightoverride
The VRAM‑driven reduction takes precedence over themax_frames_in_flightsetting inGoCL.conf. The typical order inInterceptedCreateSwapchainKHRis:
- Set
modInfo.minImageCount = max_frames_in_flight(if >0).- Clamp to surface capabilities.
- If VRAM < 256 MB, overwrite with a safe value (≥2, within surface limits).
Example:
max_frames_in_flight = 4, surface supports 2–4 images, VRAM = 200 MB → finalminImageCount = 2. The config value is ignored to save memory. Once VRAM recovers (e.g., after a level change or driver release), the next swapchain recreation will respect the configuration value again.
The heavy Basis Universal encoder is moved into gocl_transcoder.so, loaded on demand via dlopen / LoadLibrary only when an ASTC texture is encountered. The Callgrind trace lists all loaded shared objects (ob= entries) and their functions. Consequently, its static initializers are not invoked.
On Linux, the proxy is built with -Wl,-z,now to force all dynamic symbols to be resolved at load time. Expensive dynamic linking calls (dlopen, dlsym, dlclose) and their associated lazy-binding machinery appear exclusively within Vulkan initialization functions (vkCreateInstance, vkCreateDevice, layer/ICD negotiation) and are never executed inside the render-loop hot path (e.g., vkQueueSubmit, vkCmdDrawIndexed, vkCmdBeginRenderPass). Therefore, no lazy-binding overhead for a missing ASTC library occurs during rendering.
ConfigLoader / GoCL.conf)All tunable values are read once at startup via the ConfigSingleton. If GoCL.conf is absent, hard‑coded defaults are used. Settings are divided into engine‑side (affect applications linking GoCL) and proxy‑layer (affect injected games). Both groups reside in the same file.
The test suite covers:
The Sascha Willems Vulkan examples (e.g., triangle) can be used to validate the full pipeline (descriptor sets, UBO, projection, swapchain) visually. Build them separately from their repository.