Skip to content

Feat: implement surface pre-rotation #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: 1.20.x
Choose a base branch
from
Open
80 changes: 76 additions & 4 deletions src/main/java/net/vulkanmod/vulkan/Renderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static org.lwjgl.system.MemoryStack.stackGet;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.vulkan.EXTDebugUtils.*;
import static org.lwjgl.vulkan.KHRSurface.*;
import static org.lwjgl.vulkan.KHRSwapchain.*;
import static org.lwjgl.vulkan.VK10.*;

Expand Down Expand Up @@ -543,7 +544,15 @@ public static void clearAttachments(int v, int width, int height) {

public static void setViewport(int x, int y, int width, int height) {
try(MemoryStack stack = stackPush()) {
VkExtent2D transformedExtent = transformToExtent(VkExtent2D.calloc(stack), width, height);
VkOffset2D transformedOffset = transformToOffset(VkOffset2D.calloc(stack), x, y, width, height);
VkViewport.Buffer viewport = VkViewport.calloc(1, stack);

x = transformedOffset.x();
y = transformedOffset.y();
width = transformedExtent.width();
height = transformedExtent.height();

viewport.x(x);
viewport.y(height + y);
viewport.width(width);
Expand All @@ -553,20 +562,83 @@ public static void setViewport(int x, int y, int width, int height) {

VkRect2D.Buffer scissor = VkRect2D.malloc(1, stack);
scissor.offset(VkOffset2D.malloc(stack).set(0, 0));
scissor.extent(VkExtent2D.malloc(stack).set(width, height));
scissor.extent(transformedExtent);

vkCmdSetViewport(INSTANCE.currentCmdBuffer, 0, viewport);
vkCmdSetScissor(INSTANCE.currentCmdBuffer, 0, scissor);
}
}

/**
* Transform the X/Y coordinates from Minecraft coordinate space to Vulkan coordinate space
* and write them to VkOffset2D
* @param offset2D the offset to which the coordinates should be written
* @param x the X coordinate
* @param y the Y coordinate
* @param w the viewport/scissor operation width
* @param h the viewport/scissor operation height
* @return same offset2D with transformations applied as necessary
*/
private static VkOffset2D transformToOffset(VkOffset2D offset2D, int x, int y, int w, int h) {
int pretransformFlags = Vulkan.getPretransformFlags();
if(pretransformFlags == 0) {
offset2D.set(x, y);
return offset2D;
}
Framebuffer boundFramebuffer = Renderer.getInstance().boundFramebuffer;
int framebufferWidth = boundFramebuffer.getWidth();
int framebufferHeight = boundFramebuffer.getHeight();
switch (pretransformFlags) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR -> {
offset2D.x(framebufferWidth - h - y);
offset2D.y(x);
}
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR -> {
offset2D.x(framebufferWidth - w - x);
offset2D.y(framebufferHeight - h - y);
}
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR -> {
offset2D.x(y);
offset2D.y(framebufferHeight - w - x);
}
default -> {
offset2D.x(x);
offset2D.y(y);
}
}
return offset2D;
}

/**
* Transform the width and height from Minecraft coordinate space to the Vulkan coordinate space
* and write them to VkExtent2D
* @param extent2D the extent to which the values should be written
* @param w the viewport/scissor operation width
* @param h the viewport/scissor operation height
* @return the same VkExtent2D with transformations applied as necessary
*/
private static VkExtent2D transformToExtent(VkExtent2D extent2D, int w, int h) {
int pretransformFlags = Vulkan.getPretransformFlags();
if(pretransformFlags == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
pretransformFlags == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
return extent2D.set(h, w);
}
return extent2D.set(w, h);
}

public static void setScissor(int x, int y, int width, int height) {
try(MemoryStack stack = stackPush()) {
int framebufferHeight = Renderer.getInstance().boundFramebuffer.getHeight();
VkExtent2D extent = VkExtent2D.malloc(stack);
Framebuffer boundFramebuffer = Renderer.getInstance().boundFramebuffer;
// Since our x and y are still in Minecraft's coordinate space, pre-transform the framebuffer's width and height to get expected results.
transformToExtent(extent, boundFramebuffer.getWidth(), boundFramebuffer.getHeight());
int framebufferHeight = extent.height();

VkRect2D.Buffer scissor = VkRect2D.malloc(1, stack);
scissor.offset(VkOffset2D.malloc(stack).set(x, framebufferHeight - (y + height)));
scissor.extent(VkExtent2D.malloc(stack).set(width, height));
// Use this corrected height to transform from OpenGL to Vulkan coordinate space.
scissor.offset(transformToOffset(VkOffset2D.malloc(stack), x, framebufferHeight - (y + height), width, height));
// Reuse the extent to transform the scissor width/height
scissor.extent(transformToExtent(extent, width, height));

vkCmdSetScissor(INSTANCE.currentCmdBuffer, 0, scissor);
}
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/net/vulkanmod/vulkan/VRenderSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,22 @@ public static void applyModelViewMatrix(Matrix4f mat) {
}

public static void applyProjectionMatrix(Matrix4f mat) {
mat.get(projectionMatrix.buffer.asFloatBuffer());
Matrix4f pretransformMatrix = Vulkan.getPretransformMatrix();
FloatBuffer projMatrixBuffer = projectionMatrix.buffer.asFloatBuffer();
// This allows us to skip allocating an object
// if the matrix is known to be an identity matrix.
// Tbh idk if the jvm will just optimize out the allocation but i can't be sure
// as java is sometimes pretty pedantic about object allocations.
if((pretransformMatrix.properties() & Matrix4f.PROPERTY_IDENTITY) != 0) {
mat.get(projMatrixBuffer);
} else {
mat.mulLocal(pretransformMatrix, new Matrix4f()).get(projMatrixBuffer);
}
}

public static void calculateMVP() {
org.joml.Matrix4f MV = new org.joml.Matrix4f(modelViewMatrix.buffer.asFloatBuffer());
org.joml.Matrix4f P = new org.joml.Matrix4f(projectionMatrix.buffer.asFloatBuffer());

P.mul(MV).get(MVP.buffer);
}

Expand Down
10 changes: 8 additions & 2 deletions src/main/java/net/vulkanmod/vulkan/Vulkan.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
import net.vulkanmod.vulkan.memory.MemoryManager;
import net.vulkanmod.vulkan.memory.MemoryTypes;
import net.vulkanmod.vulkan.memory.StagingBuffer;
import net.vulkanmod.vulkan.queue.GraphicsQueue;
import net.vulkanmod.vulkan.queue.Queue;
import net.vulkanmod.vulkan.queue.TransferQueue;
import net.vulkanmod.vulkan.shader.Pipeline;
import net.vulkanmod.vulkan.texture.VulkanImage;
import net.vulkanmod.vulkan.util.VUtil;
import org.joml.Matrix4f;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.vma.VmaAllocatorCreateInfo;
Expand Down Expand Up @@ -462,6 +461,13 @@ public static VkExtent2D getSwapchainExtent()
return swapChain.getExtent();
}

public static Matrix4f getPretransformMatrix() {
return swapChain.getPretransformMatrix();
}
public static int getPretransformFlags() {
return swapChain.getPretransformFlags();
}

public static List<VulkanImage> getSwapChainImages() { return swapChain.getImages(); }

public static long getCommandPool()
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/net/vulkanmod/vulkan/framebuffer/SwapChain.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import net.vulkanmod.vulkan.Vulkan;
import net.vulkanmod.vulkan.queue.Queue;
import net.vulkanmod.vulkan.texture.VulkanImage;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;

Expand Down Expand Up @@ -39,6 +40,12 @@ public static int getDefaultDepthFormat() {
private long swapChain = VK_NULL_HANDLE;
private List<VulkanImage> swapChainImages;
private VkExtent2D extent2D;
// A matrix that describes the transformations that should be applied
// to the output of the game.
private Matrix4f pretransformMatrix = new Matrix4f();
// The pretransform flags that were given to the swapchain,
// masked (see "setupPreRotation(VkExtent2D, VkSurfaceCapabilitiesKHR)")
private int pretransformFlags;
public boolean isBGRAformat;
private boolean vsync = false;

Expand Down Expand Up @@ -83,6 +90,7 @@ public void createSwapChain() {
VkSurfaceFormatKHR surfaceFormat = getFormat(surfaceProperties.formats);
int presentMode = getPresentMode(surfaceProperties.presentModes);
VkExtent2D extent = getExtent(surfaceProperties.capabilities);
setupPreRotation(extent, surfaceProperties.capabilities);

if(extent.width() == 0 && extent.height() == 0) {
if(swapChain != VK_NULL_HANDLE) {
Expand Down Expand Up @@ -327,6 +335,14 @@ public VkExtent2D getExtent() {
return extent2D;
}

public Matrix4f getPretransformMatrix(){
return pretransformMatrix;
}

public int getPretransformFlags() {
return pretransformFlags;
}

public VulkanImage getColorAttachment() {
return this.swapChainImages.get(Renderer.getCurrentFrame());
}
Expand Down Expand Up @@ -393,6 +409,38 @@ private static VkExtent2D getExtent(VkSurfaceCapabilitiesKHR capabilities) {
return actualExtent;
}

private void setupPreRotation(VkExtent2D extent, VkSurfaceCapabilitiesKHR surfaceCapabilities) {
// Mask off anything else that does not interest us in the transform
pretransformFlags = surfaceCapabilities.currentTransform() &
(VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR |
VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR |
VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR);
int rotateDegrees = 0;
boolean swapXY = false;
switch (pretransformFlags) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR -> {
rotateDegrees = 90;
swapXY = true;
}
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR -> {
rotateDegrees = 270;
swapXY = true;
}
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR -> rotateDegrees = 180;
}
pretransformMatrix = pretransformMatrix.identity();
if(rotateDegrees != 0) {
pretransformMatrix.rotate((float) Math.toRadians(rotateDegrees), 0, 0, 1);
pretransformMatrix.invert();
}
if(swapXY) {
int originalWidth = extent.width();
int originalHeight = extent.height();
extent.width(originalHeight);
extent.height(originalWidth);
}
}

public boolean isVsync() {
return vsync;
}
Expand Down