refactor(mod): dispatch RenderBackendOps.current() by active backend (Stage 4 kickoff)

Stage 4 Vulkan groundwork. Before this, RenderBackendOps.current() unconditionally
returned GlRenderBackendOps.INSTANCE with a 'Stage 3-prep' comment. Under Vulkan
the GL impl is still harmless (its methods all short-circuit on OptimizeToggles
and the GL mixins never fire), but the interface lied about the active backend.

- New misc/optimize/vulkan/VulkanRenderBackendOps: singleton, no-op stub, all
  *Supported() return false, cache/map methods do nothing. This is the skeleton
  the Stage 4 items (persistent VkPipelineCache, VulkanRenderPass state cache)
  will grow into.
- RenderBackendOps.current() inspects RenderSystem.getDevice().getClass().getName()
  and returns VulkanRenderBackendOps for com.mojang.blaze3d.vulkan.* devices,
  GlRenderBackendOps otherwise. Class-name check (not instanceof) so this method
  itself does not force linking of GlDevice/VulkanDevice.
- Wrapped getDevice() in try/catch: unit tests call current() before RenderSystem
  has a device and previously took the GL path; now they still do via the fallback.

Doc: Stage 4 kickoff entry in the changelog.
This commit is contained in:
loki5512344 2026-10-12 22:10:10 +02:00
parent 39b270b108
commit d5f17eb0ac
Signed by: boba
GPG key ID: 253067914055423B
3 changed files with 129 additions and 1 deletions

View file

@ -3,6 +3,12 @@
Статус: Этапы 1-3 готовы (2026-10-12). Этап 4 (Vulkan) — стартуем с универсально-полезных пунктов. Цель MC: 26.2.
## История изменений
- **2026-10-12**: Этап 4 старт — `RenderBackendOps.current()` теперь определяет активный бэкенд через
`RenderSystem.getDevice().getClass().getName()` (class-name, не `instanceof`, чтобы не форсировать линковку
`GlDevice`/`VulkanDevice`); добавлен `misc/optimize/vulkan/VulkanRenderBackendOps` — no-op-заглушка со
всеми `*Supported() == false`. Под GL всё работает как раньше; под Vulkan mixins из `mixins/optimize/gl/*` не
загружаются, и теперь хотя бы интерфейс честно возвращает правильный бэкенд. Живые Vulkan-пути
(`VkPipelineCache`, state cache) — следующий шаг.
- **2026-10-12**: убран гейт «меряем на Windows до включения». Бенчмарки больше не блокируют работу: универсально-полезные
GL/Vulkan-оптимизации делаем сразу за default-off тумблерами (пользователь включает сам), отчёт цифр остаётся как
факультативная задача владельца и не входит в план. Этап 3 закрыт по всем пунктам, кроме п.9 (`no_item_sort` /

View file

@ -1,5 +1,8 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.gl;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.loki.lovisual.features.module.modules.misc.optimize.vulkan.VulkanRenderBackendOps;
/**
* Backend-agnostic surface for the optimization layer's GL/Vulkan hooks.
*
@ -29,8 +32,30 @@ package dev.loki.lovisual.features.module.modules.misc.optimize.gl;
*/
public interface RenderBackendOps {
/** Resolve the current backend — GL backend if GL is active, otherwise {@code null}. */
/**
* Resolve the current backend by inspecting the active {@code GpuDeviceBackend}.
* Class-name check (not {@code instanceof}) so this method itself does not
* force the loader to link {@code GlDevice} or {@code VulkanDevice}; the
* Blaze3D jar always ships both, but keeping the check by string means the
* dispatch stays correct even when only one backend's class has been
* resolved. Falls back to the GL impl when the device is null (early boot)
* so mixins' {@code supported()} short-circuits behave the same as before.
*/
static RenderBackendOps current() {
Object device;
try {
device = RenderSystem.getDevice();
} catch (Throwable ignored) {
// Tests and pre-init paths call current() before RenderSystem has a
// device; fall through to GL so existing unit tests keep passing.
device = null;
}
if (device != null) {
String name = device.getClass().getName();
if (name.startsWith("com.mojang.blaze3d.vulkan.")) {
return VulkanRenderBackendOps.current();
}
}
return GlRenderBackendOps.current();
}

View file

@ -0,0 +1,97 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.vulkan;
import dev.loki.lovisual.features.module.modules.misc.optimize.gl.RenderBackendOps;
/**
* Vulkan-side implementation of {@link RenderBackendOps}. All GPU-object ops
* (lazy clear, TBO cache, shared depth FBO, RenderTypes cache) currently live
* in the GL mixin set — under Vulkan those mixins never load, so this class is
* a no-op placeholder that lets {@code RenderBackendOps.current()} dispatch by
* actual backend instead of unconditionally returning the GL impl.
*
* <p>Every {@code *Supported} reports {@code false}. Callers already gate on
* supported before attempting, so the effect is the same as "Optimize GPU
* features off" for backend-dependent items. Stage 4 adds Vulkan-specific
* paths (persistent {@code VkPipelineCache}, {@code VulkanRenderPass} state
* cache) by flipping the corresponding {@code *Supported} here to true and
* implementing the op body.
*
* <p>Doc reference: {@code mod/docs/optimize-layer-design.md}, "Этап 4" and
* "Что уже решено в архитектуре" → "Рендер-бэкенды".
*/
public final class VulkanRenderBackendOps implements RenderBackendOps {
private static final VulkanRenderBackendOps INSTANCE = new VulkanRenderBackendOps();
private VulkanRenderBackendOps() {
}
/** Singleton — no state, no per-device instance needed. */
public static VulkanRenderBackendOps current() {
return INSTANCE;
}
// --- Lazy clear ---
@Override
public boolean lazyClearSupported() {
return false;
}
@Override
public boolean skipBindFramebufferZero(int framebuffer) {
return false;
}
// --- TBO cache ---
@Override
public boolean tboCacheSupported() {
return false;
}
@Override
public void trackBoundTboTexture(int texture) {
}
@Override
public boolean tboAlreadyAttached(int format, int buffer) {
return false;
}
@Override
public void markTboAttached(int format, int buffer) {
}
@Override
public void forgetTboCache() {
}
// --- FBO share ---
@Override
public boolean fboShareSupported() {
return false;
}
@Override
public boolean clearDepthInPlace(int depthId, double value) {
return false;
}
// --- RenderTypes cache ---
@Override
public boolean renderTypesCacheSupported() {
return false;
}
@Override
public Object renderTypeFor(Identifier id) {
return null;
}
@Override
public void cacheRenderType(Identifier id, Object type) {
}
}