That's a known issue with how we handle D3D9 fixed function rendering.
Now I've seen for myself how problematic DX9 > DXVK is in Lineage 2 when I tried to make an AO. There are so many pitfalls and compatibility issues. I'm thinking about trying DX9 > DX11 > DXVK, too, to see if it improves the inject.
Try with current master https://github.com/doitsujin/dxvk/actions/runs/24126385952
Improvements have been made in regards to stuttering with fixed function.
Try with current master https://github.com/doitsujin/dxvk/actions/runs/24126385952 Improvements have been made in regards to stuttering with fixed function.
Thank you. I have Lineage 2 with +- stable FPS. I wrote more about the difficulty of working with the wrapper and this game. I'm currently trying to make a DXVK fork with AMD CACAO for this game.
What issues are you having? And on what setup
What issues are you having? And on what setup
Thank you, I don't need anything from you for my task. I've implemented everything I need in the local fork. However, there are some points regarding any AO mods for DX9. Below is a brief summary from the agent and examples of vide code, but I hope you get the general idea.
Context: This document describes the general API requirements for integrating SSAO/AO effects (AMD CACAO, NVIDIA HBAO+, RTGI, MXAO, etc.) into D3D9 games through DXVK. These requirements are game-agnostic and apply to any compute-based post-processing AO effect.
Location: src/d3d9/d3d9_swapchain.cpp → D3D9SwapChainEx::Present()
Requirement: A mechanism to execute compute dispatch before the present blit.
Why needed: All SSAO/AO effects are post-processing compute pipelines that must execute before the final image is presented to the swapchain.
Proposed API:
// In d3d9_swapchain.h
using PresentHookFn = void(*)(DxvkContext* ctx,
uint32_t width,
uint32_t height,
Rc<DxvkImage> backBuffer);
void RegisterPresentHook(PresentHookFn hook);
// In d3d9_swapchain.cpp::Present()
if (m_presentHook) {
m_presentHook(ctx,
swapImage->info().extent.width,
swapImage->info().extent.height,
backBuffer);
}
Implementation notes:
DxvkContext* for emitting compute commandsvkQueuePresentKHR is calledLocation: src/d3d9/d3d9_common_texture.cpp → depth-stencil surface creation
Requirement: Ability to sample the depth buffer from compute shaders.
Why needed: All SSAO/AO algorithms require reading the depth buffer to compute occlusion factors.
Current limitation: Depth-stencil surfaces are created with only VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT. Compute shaders cannot sample them.
Required change:
// In d3d9_common_texture.cpp
// Always add SAMPLED_BIT for depth-stencil surfaces:
if (isDS) {
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT; // <-- for AO sampling
}
Additional requirements:
D3DFMT_D24S8 → VK_FORMAT_D32_SFLOAT_S8_UINT with sampled supportVkImage handle from IDirect3DSurface9 (depth-stencil surface)Proposed API:
// In d3d9_surface.h
VkImage GetDepthBufferImage() const;
VkFormat GetDepthBufferFormat() const;
uint32_t GetDepthBufferSampleCount() const;
Location: src/d3d9/d3d9_device.cpp → transform state and constant buffer handling
Requirement: Ability to obtain the exact projection matrix for unprojecting depth values to view space.
Why needed: Without an accurate projection matrix, it is impossible to convert depth buffer values to view-space positions, which is required for any screen-space AO technique.
Critical issue: Many D3D9 games (UE2, UE3, Source Engine, etc.) use vertex shaders where the projection matrix is stored in vertex shader constant buffers, NOT in SetTransform(D3DTS_PROJECTION).
Required changes:
// In d3d9_device.cpp
// Intercept VS constants to detect projection matrix pattern
HRESULT SetVertexShaderConstantF(UINT StartRegister,
const float* pConstantData,
UINT Vector4fCount) {
// Search for projection matrix pattern in constants
if (IsProjectionMatrixPattern(pConstantData, Vector4fCount)) {
m_cachedProjMatrix = ExtractProjectionMatrix(pConstantData);
m_hasValidProj = true;
}
return ...;
}
Projection matrix detection heuristics:
m[0][0] = 1/(aspect * tan(fov/2))m[1][1] = 1/tan(fov/2)m[2][2] = far/(far-near) or similar depth encodingm[3][2] = -near*far/(far-near) or -nearProposed API:
// In d3d9_device.h
const Matrix4& GetProjectionMatrix() const;
bool HasValidProjectionMatrix() const;
Location: src/dxvk/shaders/dxvk_present_common.glsl → composite_image()
Requirement: Flexible AO compositing formula in the present blitter shader.
Why needed: Different games require different compositing approaches:
Current limitation: The blitter uses a fixed formula: color.rgb *= max(0.0, 1.0 - ao). This causes severe double-darkening in games with pre-baked ambient occlusion in lightmaps.
Required change:
// In dxvk_present_common.glsl
layout(constant_id = 8) const bool c_composite_ao = false;
layout(constant_id = 9) const bool c_ao_debug_view = false;
layout(constant_id = 10) const int c_ao_blend_mode = 0;
// 0 = multiplicative (default)
// 1 = luma-masked (for baked lighting)
// 2 = additive (for RTGI/bounced light)
vec4 composite_image(vec4 color) {
ivec2 coord = ivec2(gl_FragCoord.xy);
if (c_composite_ao) {
float ao = texelFetch(s_ao, coord, 0).r;
if (c_ao_debug_view) {
color.rgb = vec3(ao); // Debug: show raw AO
} else if (c_ao_blend_mode == 0) {
// Multiplicative: standard AO
color.rgb *= max(0.0, 1.0 - ao);
} else if (c_ao_blend_mode == 1) {
// Luma-masked: avoid double-darkening baked lighting
float luma = dot(color.rgb, vec3(0.2126, 0.5870, 0.1140));
float mask = smoothstep(0.05, 0.30, luma);
color.rgb *= (1.0 - ao * mask);
} else if (c_ao_blend_mode == 2) {
// Additive: for RTGI/bounced light effects
color.rgb -= ao * 0.5;
}
}
// ... rest of composite (HUD, cursor, etc.)
return color;
}
Configuration:
dxvk.confLocation: src/dxvk/dxvk_swapchain_blitter.cpp → descriptor set setup
Requirement: Mechanism to pass the AO buffer from the compute pipeline to the present blitter.
Why needed: The blitter must read the AO buffer to composite it onto the final frame.
Required change:
// In dxvk_swapchain_blitter.h
void SetAoView(Rc<DxvkImageView> aoView, bool debugView);
// In dxvk_swapchain_blitter.cpp
// Add binding 4 to the blitter descriptor set layout:
// binding 4: sampled image (AO buffer)
// In present():
if (m_aoView) {
descriptors[4] = m_aoView->getDescriptor();
ctx->track(m_aoView->image(), DxvkAccess::Read);
}
Implementation notes:
VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL| # | Requirement | File(s) | Priority | Lines of code |
|---|---|---|---|---|
| 1 | Present Hook API | d3d9_swapchain.cpp, d3d9_swapchain.h |
🔴 Critical | ~30 |
| 2 | Depth Buffer SAMPLED_BIT | d3d9_common_texture.cpp, d3d9_surface.h |
🔴 Critical | ~10 |
| 3 | Projection Matrix Access | d3d9_device.cpp, d3d9_device.h |
🔴 Critical | ~50 |
| 4 | AO Compositing Formulas | dxvk_present_common.glsl |
🟡 Important | ~20 |
| 5 | Descriptor Set Binding | dxvk_swapchain_blitter.cpp, dxvk_swapchain_blitter.h |
🟡 Important | ~15 |
Total estimated effort: ~125 lines of code across 6 files.
All changes are localized to the D3D9 backend and present blitter. They are backward-compatible and do not affect existing functionality.
A working implementation of all 5 requirements is available in a private development fork with integrated AMD FidelityFX CACAO SSAO pipeline.
Key files in the implementation:
src/d3d9/d3d9_swapchain.cpp — Present hook integrationsrc/d3d9/d3d9_cacao.cpp — Full CACAO compute pipeline (26 SPIR-V shaders from FFX SDK)src/d3d9/d3d9_device.cpp — Projection matrix caching from SetTransform + VS constantssrc/dxvk/shaders/dxvk_present_common.glsl — AO compositing with luma-masked blendingsrc/d3d9/d3d9_common_texture.cpp — SAMPLED_BIT for depth-stencil surfacesTested on:
For AO mod support in DXVK's D3D9 backend, 5 changes are needed (3 critical, 2 important). All changes are backward-compatible, localized to the D3D9 backend and present blitter, and do not require any Vulkan API changes or driver modifications.
Without these changes, compute-based SSAO/AO mods are impossible in DXVK for D3D9 games. With these changes, any AO technique (CACAO, HBAO+, RTGI, MXAO, etc.) can be integrated as an external mod without modifying DXVK's core code.
Nothing extracted yet.
Software information
Lineage 2 very low settings
(Additionally, I tested the UT2004 demo, it had the same problem.)
System information №1 RIG:
System information №2 RIG:
Lenovo IdeaPad 1 15IAU7
Apitrace file(s)
For instructions on how to use apitrace, see: https://github.com/doitsujin/dxvk/wiki/Using-Apitrace
Lineage2 game has the worst optimization and performance of any existing game on earth. DXVK allows you to increase performance and the biggest difference is on an integrated Intel video card, because... The initial Intel UHD performance in this game is very poor compared to nvidia video cards. Unfortunately, a side effect is a huge number of stutters.
Today I found out about another wrapper dgVoodoo2_82_5 and decided to test it on Intel UHD (i3-1215u), performance increased but not to the level of DX9-DXVK, and on 1660super the effect is completely opposite - performance drops. But what surprised me was that when translate DX9=>DX11=>DXVK there are absolutely no stutters. Therefore, I had a question: Why do stutters appear specifically when translate from DX9 to DXVK?