Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,71 @@ jobs:
name: plume-macos-${{ matrix.type }}-${{ matrix.os }}
path: |
cmake-build/bin/plume_triangle

test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-latest, macos-15]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ runner.os }}-plume-tests-ccache

- name: Install Windows Dependencies
if: runner.os == 'Windows'
run: |
choco install ninja
Remove-Item -Path "C:\ProgramData\Chocolatey\bin\ccache.exe" -Force -ErrorAction SilentlyContinue

# mesa-vulkan-drivers brings lavapipe, a software Vulkan driver.
- name: Install Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y ninja-build libx11-dev mesa-vulkan-drivers

- name: Install macOS Dependencies
if: runner.os == 'macOS'
run: |
brew install ninja

- name: Configure Developer Command Prompt
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1

- name: Build Tests (Unix)
if: runner.os != 'Windows'
run: |-
# enable ccache
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"

cmake -DCMAKE_BUILD_TYPE=Debug -DPLUME_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
cmake --build cmake-build --config Debug --target plume_tests

- name: Build Tests (Windows)
if: runner.os == 'Windows'
run: |-
$cpuCores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors

# remove LLVM from PATH so it doesn't overshadow the one provided by VS
$env:PATH = ($env:PATH -split ';' | Where-Object { $_ -ne 'C:\Program Files\LLVM\bin' }) -join ';'

cmake -DCMAKE_BUILD_TYPE=Debug -DPLUME_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
cmake --build cmake-build --config Debug --target plume_tests -j $cpuCores

- name: Run Tests
if: runner.os != 'Windows'
run: ./cmake-build/bin/plume_tests

# There is no GPU on the runners, so D3D12 runs on WARP.
- name: Run Tests (Windows)
if: runner.os == 'Windows'
run: ./cmake-build/bin/plume_tests.exe --device "Microsoft Basic Render Driver"
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ cmake_dependent_option(PLUME_SDL3_VULKAN_ENABLED "Enable SDL3 Vulkan integration
cmake_dependent_option(PLUME_D3D12_AGILITY_SDK_ENABLED "Enable D3D12 Agility SDK" OFF WIN32 OFF)
cmake_dependent_option(PLUME_APPLE_RETINA_ENABLED "Enable Apple Retina display support" ON APPLE ON)
option(PLUME_BUILD_EXAMPLES "Build example applications" OFF)
option(PLUME_BUILD_TESTS "Build correctness tests" OFF)

# Windows-specific definitions
if(WIN32)
Expand Down Expand Up @@ -59,6 +60,7 @@ message(STATUS "Plume - SDL2 Vulkan integration: ${PLUME_SDL2_VULKAN_ENABLED}")
message(STATUS "Plume - SDL3 Vulkan integration: ${PLUME_SDL3_VULKAN_ENABLED}")
message(STATUS "Plume - D3D12 Agility SDK: ${PLUME_D3D12_AGILITY_SDK_ENABLED}")
message(STATUS "Plume - Building examples: ${PLUME_BUILD_EXAMPLES}")
message(STATUS "Plume - Building tests: ${PLUME_BUILD_TESTS}")

# Basic source files that are always included
set(PLUME_SOURCES
Expand Down Expand Up @@ -128,3 +130,8 @@ endif()
if(PLUME_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

# Add tests if requested
if(PLUME_BUILD_TESTS)
add_subdirectory(tests)
endif()
15 changes: 11 additions & 4 deletions plume_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2312,7 +2312,13 @@ namespace plume {

const D3D12_TEXTURE_COPY_LOCATION copyDstLocation = toD3D12(dstLocation);
const D3D12_TEXTURE_COPY_LOCATION copySrcLocation = toD3D12(srcLocation);
setSamplePositions(dstLocation.texture);

const bool hasDestinationTexture = (dstLocation.texture != nullptr);

if (hasDestinationTexture) {
setSamplePositions(dstLocation.texture);
}

d3d->CopyTextureRegion(&copyDstLocation, dstX, dstY, dstZ, &copySrcLocation, (srcBox != nullptr) ? &copyBox : nullptr);
resetSamplePositions();
}
Expand Down Expand Up @@ -3654,8 +3660,10 @@ namespace plume {
DXGI_ADAPTER_DESC1 adapterDesc;
adapterOption->GetDesc1(&adapterDesc);

// Ignore remote or software adapters.
if (adapterDesc.Flags & (DXGI_ADAPTER_FLAG_REMOTE | DXGI_ADAPTER_FLAG_SOFTWARE)) {
// Ignore remote adapters, and software adapters unless one was requested by name.
std::string deviceName = Utf16ToUtf8(adapterDesc.Description);
bool softwareAdapter = (adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0;
if ((adapterDesc.Flags & DXGI_ADAPTER_FLAG_REMOTE) || (softwareAdapter && (deviceName != preferredDeviceName))) {
adapterOption->Release();
continue;
}
Expand Down Expand Up @@ -3738,7 +3746,6 @@ namespace plume {
}

// Pick this adapter and device if it has better feature support than the current one.
std::string deviceName = Utf16ToUtf8(adapterDesc.Description);
bool preferOverNothing = (adapter == nullptr) || (d3d == nullptr);
bool preferVideoMemory = adapterDesc.DedicatedVideoMemory > description.dedicatedVideoMemory;
bool preferUserChoice = preferredDeviceName == deviceName;
Expand Down
35 changes: 35 additions & 0 deletions plume_metal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,41 @@ namespace plume {
dstOrigin
);
activeBlitEncoder->popDebugGroup();
} else if (dstLocation.type == RenderTextureCopyType::PLACED_FOOTPRINT && srcLocation.type == RenderTextureCopyType::SUBRESOURCE) {
assert(dstBuffer != nullptr);
assert(srcTexture != nullptr);

// Calculate block size based on source texture format
const uint32_t blockWidth = RenderFormatBlockWidth(srcTexture->desc.format);

const uint32_t mipLevel = srcLocation.subresource.mipLevel;
const MTL::Size size = (srcBox != nullptr)
? MTL::Size { NS::UInteger(srcBox->right - srcBox->left), NS::UInteger(srcBox->bottom - srcBox->top), NS::UInteger(srcBox->back - srcBox->front) }
: MTL::Size { std::max(srcTexture->desc.width >> mipLevel, 1U), std::max(srcTexture->desc.height >> mipLevel, 1U), std::max(srcTexture->desc.depth >> mipLevel, 1U) };

const uint32_t horizontalBlocks = (dstLocation.placedFootprint.rowWidth + blockWidth - 1) / blockWidth;
const uint32_t verticalBlocks = (dstLocation.placedFootprint.height + blockWidth - 1) / blockWidth;
const uint32_t bytesPerRow = horizontalBlocks * RenderFormatSize(srcTexture->desc.format);
const uint32_t bytesPerImage = bytesPerRow * verticalBlocks;
const uint64_t dstOffset = dstLocation.placedFootprint.offset + dstZ * bytesPerImage + (dstY / blockWidth) * bytesPerRow + (dstX / blockWidth) * RenderFormatSize(srcTexture->desc.format);

const MTL::Origin srcOrigin = (srcBox != nullptr)
? MTL::Origin { NS::UInteger(srcBox->left), NS::UInteger(srcBox->top), NS::UInteger(srcBox->front) }
: MTL::Origin { 0, 0, 0 };

activeBlitEncoder->pushDebugGroup(MTLSTR("CopyTextureRegion"));
activeBlitEncoder->copyFromTexture(
srcTexture->mtl,
srcLocation.subresource.arrayIndex,
srcLocation.subresource.mipLevel,
srcOrigin,
size,
dstBuffer->mtl,
dstOffset,
bytesPerRow,
bytesPerImage
);
activeBlitEncoder->popDebugGroup();
} else {
assert(dstTexture != nullptr);
assert(srcTexture != nullptr);
Expand Down
25 changes: 25 additions & 0 deletions plume_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3235,6 +3235,31 @@ namespace plume {
imageCopy.imageExtent.depth = srcLocation.placedFootprint.depth;
vkCmdCopyBufferToImage(vk, srcBuffer->vk, dstTexture->vk, toImageLayout(dstTexture->textureLayout), 1, &imageCopy);
}
else if ((dstLocation.type == RenderTextureCopyType::PLACED_FOOTPRINT) && (srcLocation.type == RenderTextureCopyType::SUBRESOURCE)) {
assert(dstBuffer != nullptr);
assert(srcTexture != nullptr);

const uint32_t blockWidth = RenderFormatBlockWidth(srcTexture->desc.format);
const uint32_t blockSize = RenderFormatSize(srcTexture->desc.format);
const uint32_t bytesPerRow = ((dstLocation.placedFootprint.rowWidth + blockWidth - 1) / blockWidth) * blockSize;
const uint32_t bytesPerImage = bytesPerRow * ((dstLocation.placedFootprint.height + blockWidth - 1) / blockWidth);
const uint32_t mipLevel = srcLocation.subresource.mipLevel;
VkBufferImageCopy imageCopy = {};
imageCopy.bufferOffset = dstLocation.placedFootprint.offset + dstZ * bytesPerImage + (dstY / blockWidth) * bytesPerRow + (dstX / blockWidth) * blockSize;
imageCopy.bufferRowLength = ((dstLocation.placedFootprint.rowWidth + blockWidth - 1) / blockWidth) * blockWidth;
imageCopy.bufferImageHeight = ((dstLocation.placedFootprint.height + blockWidth - 1) / blockWidth) * blockWidth;
imageCopy.imageSubresource.aspectMask = toAspectFlags(srcTexture->desc.format, srcTexture->desc.flags);
imageCopy.imageSubresource.baseArrayLayer = srcLocation.subresource.arrayIndex;
imageCopy.imageSubresource.layerCount = 1;
imageCopy.imageSubresource.mipLevel = srcLocation.subresource.mipLevel;
imageCopy.imageOffset.x = srcBox != nullptr ? srcBox->left : 0;
imageCopy.imageOffset.y = srcBox != nullptr ? srcBox->top : 0;
imageCopy.imageOffset.z = srcBox != nullptr ? srcBox->front : 0;
imageCopy.imageExtent.width = srcBox != nullptr ? uint32_t(srcBox->right - srcBox->left) : std::max(srcTexture->desc.width >> mipLevel, 1U);
imageCopy.imageExtent.height = srcBox != nullptr ? uint32_t(srcBox->bottom - srcBox->top) : std::max(srcTexture->desc.height >> mipLevel, 1U);
imageCopy.imageExtent.depth = srcBox != nullptr ? uint32_t(srcBox->back - srcBox->front) : std::max(srcTexture->desc.depth >> mipLevel, 1U);
vkCmdCopyImageToBuffer(vk, srcTexture->vk, toImageLayout(srcTexture->textureLayout), dstBuffer->vk, 1, &imageCopy);
}
else {
VkImageCopy imageCopy = {};
imageCopy.srcSubresource.aspectMask = toAspectFlags(srcTexture->desc.format, srcTexture->desc.flags);
Expand Down
34 changes: 34 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
if(PLUME_SDL2_VULKAN_ENABLED OR PLUME_SDL3_VULKAN_ENABLED)
message(FATAL_ERROR "Plume - The tests create the Vulkan interface without a window, which requires SDL Vulkan integration to be disabled")
endif()

include(${CMAKE_SOURCE_DIR}/examples/cmake/PlumeShaders.cmake)
plume_shaders_init()

add_executable(plume_tests main.cpp)

target_link_libraries(plume_tests PRIVATE plume)

plume_compile_vertex_shader(plume_tests "${CMAKE_CURRENT_SOURCE_DIR}/shaders/color.vert.hlsl" "testColorVert" "VSMain")
plume_compile_pixel_shader(plume_tests "${CMAKE_CURRENT_SOURCE_DIR}/shaders/color.frag.hlsl" "testColorFrag" "PSMain")
plume_compile_vertex_shader(plume_tests "${CMAKE_CURRENT_SOURCE_DIR}/shaders/fullscreen.vert.hlsl" "testFullscreenVert" "VSMain")
plume_compile_pixel_shader(plume_tests "${CMAKE_CURRENT_SOURCE_DIR}/shaders/texture.frag.hlsl" "testTextureFrag" "PSMain")

if(APPLE)
target_link_libraries(plume_tests PRIVATE "-framework Metal -framework QuartzCore -framework CoreGraphics -framework Foundation -framework IOKit")
elseif(WIN32)
target_link_libraries(plume_tests PRIVATE d3d12 dxgi)
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
find_package(X11 REQUIRED)
target_include_directories(plume_tests PRIVATE ${X11_INCLUDE_DIR})
target_link_libraries(plume_tests PRIVATE ${X11_LIBRARIES})
endif()

target_include_directories(plume_tests PRIVATE
${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR}
)

set_target_properties(plume_tests PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
Loading
Loading