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
30 changes: 30 additions & 0 deletions examples/image_sampler/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.20)
project(kompute_image_sampler)

set(CMAKE_CXX_STANDARD 14)

# Options
option(KOMPUTE_OPT_GIT_TAG "The tag of the repo to use for the example" v0.9.0)
option(KOMPUTE_OPT_FROM_SOURCE "Whether to build example from source or from git fetch repo" ON)

if(KOMPUTE_OPT_FROM_SOURCE)
add_subdirectory(../../ ${CMAKE_CURRENT_BINARY_DIR}/kompute_build)
else()
include(FetchContent)
FetchContent_Declare(kompute GIT_REPOSITORY https://github.com/KomputeProject/kompute.git
GIT_TAG ${KOMPUTE_OPT_GIT_TAG})
FetchContent_MakeAvailable(kompute)
include_directories(${kompute_SOURCE_DIR}/src/include)
endif()

# Compiling shader
vulkan_compile_shader(
INFILE shader/upsample.comp
OUTFILE shader/upsample.hpp
NAMESPACE "shader")

add_library(shader INTERFACE "shader/upsample.hpp")
target_include_directories(shader INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)

add_executable(kompute_image_sampler src/main.cpp)
target_link_libraries(kompute_image_sampler PRIVATE shader kompute::kompute)
52 changes: 52 additions & 0 deletions examples/image_sampler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Kompute Image Sampler Example

This folder contains an end to end Kompute example that shows how to use
`kp::Image::createSampler()` to bind an image as a combined image sampler
(`sampler2D` in GLSL) instead of a plain storage image (`image2D`).

The example uploads a tiny 4x4 checkerboard "image", attaches a sampler to
it, and dispatches a compute shader that fills a larger 16x16 output image
by sampling the small input with `texture()`. Because the input image has a
sampler attached, the GPU bilinearly filters between texels, producing a
smoothly interpolated (rather than blocky/nearest) upsample -- something
that isn't possible with `imageLoad`/`imageStore` on a plain storage image.

This example is structured such that you will be able to extend it for your
project. It contains a CMake build configuration that can be used in your
production applications.

```bash
git clone https://github.com/KomputeProject/kompute.git
cd kompute/examples/image_sampler
mkdir build
cd build
cmake ..
cmake --build .
```

## Executing

Form inside the `build/` directory run:

### Linux

```bash
./kompute_image_sampler
```

### Windows

```bash
.\Debug\kompute_image_sampler.exe
```

## Pre-requisites

In order to run this example, you will need the following dependencies:

* REQUIRED
+ The Vulkan SDK must be installed

For the Vulkan SDK, the simplest way to install it is through
[their website](https://vulkan.lunarg.com/sdk/home). You just have to follow
the instructions for the relevant platform.
71 changes: 71 additions & 0 deletions examples/image_sampler/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include <iomanip>
#include <iostream>
#include <memory>
#include <vector>

#include <kompute/Kompute.hpp>
#include <shader/upsample.hpp>

int main()
{
kp::Manager mgr;

// A tiny 4x4 single-channel "image" (a coarse 2x2 checkerboard).
const uint32_t inWidth = 4;
const uint32_t inHeight = 4;
// clang-format off
std::vector<float> inputData = {
0.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f,
};
// clang-format on

std::shared_ptr<kp::ImageT<float>> inputImage =
mgr.image(inputData, inWidth, inHeight, 1);

// Attaching a sampler switches this image's descriptor type from a
// storage image (`image2D`, raw imageLoad/imageStore) to a combined
// image sampler (`sampler2D`, hardware-filtered `texture()` reads).
// createSampler() with no arguments uses Kompute's default sampler
// (linear filtering, clamp-to-edge addressing).
inputImage->createSampler();

// A larger 16x16 output image that we fill by *sampling* (not just
// copying) the small input image, letting the GPU's bilinear filter
// smoothly interpolate between input texels.
const uint32_t outWidth = 16;
const uint32_t outHeight = 16;
std::vector<float> outputData(outWidth * outHeight, 0.0f);
std::shared_ptr<kp::ImageT<float>> outputImage =
mgr.image(outputData, outWidth, outHeight, 1);

const std::vector<std::shared_ptr<kp::Memory>> params = { inputImage,
outputImage };

const std::vector<uint32_t> shader = std::vector<uint32_t>(
shader::UPSAMPLE_COMP_SPV.begin(), shader::UPSAMPLE_COMP_SPV.end());

std::shared_ptr<kp::Algorithm> algo = mgr.algorithm(
params, shader, kp::Workgroup{ outWidth, outHeight, 1 });

mgr.sequence()
->record<kp::OpSyncDevice>(params)
->record<kp::OpAlgoDispatch>(algo)
->record<kp::OpSyncLocal>(params)
->eval();

std::cout << "Upsampled output (" << outWidth << "x" << outHeight
<< ") sampled from a " << inWidth << "x" << inHeight
<< " source image:\n\n";

const std::vector<float> result = outputImage->vector();
for (uint32_t y = 0; y < outHeight; y++) {
for (uint32_t x = 0; x < outWidth; x++) {
std::cout << std::fixed << std::setprecision(2)
<< result[y * outWidth + x] << " ";
}
std::cout << "\n";
}
}
28 changes: 28 additions & 0 deletions examples/python_image_sampler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Python Image Sampler Example

This demonstrates using `Image.create_sampler()` from Python to bind an image
as a combined image sampler (`sampler2D` in GLSL) instead of a plain storage
image (`image2D`).

The script uploads a tiny 4x4 checkerboard "image", attaches a sampler to
it, and dispatches a compute shader that fills a larger 16x16 output image
by sampling the small input with `texture()`. Because the input image has a
sampler attached, the GPU bilinearly filters between texels, producing a
smoothly interpolated (rather than blocky/nearest) upsample -- something
that isn't possible with `imageLoad`/`imageStore` on a plain storage image.

To run the example:

```bash
python image_sampler.py
```

## Pre-requisites

* REQUIRED
+ The `kp` Python package (built with `KOMPUTE_OPT_BUILD_PYTHON=ON`, or
`pip install .` from the repo root)
+ `numpy`
+ `glslangValidator` available on your `PATH` (ships with the
[Vulkan SDK](https://vulkan.lunarg.com/sdk/home)), used here to
compile the inline GLSL shader at runtime
83 changes: 83 additions & 0 deletions examples/python_image_sampler/image_sampler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os

import kp
import numpy as np


def compile_source(source):
open("tmp_kp_shader.comp", "w").write(source)
os.system("glslangValidator -V tmp_kp_shader.comp -o tmp_kp_shader.comp.spv")
return open("tmp_kp_shader.comp.spv", "rb").read()


def main():
mgr = kp.Manager()

# A tiny 4x4 single-channel "image" (a coarse 2x2 checkerboard).
in_width, in_height = 4, 4
input_data = np.array(
[
[0.0, 0.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 0.0],
[1.0, 1.0, 0.0, 0.0],
],
dtype=np.float32,
)

input_image = mgr.image(input_data, in_width, in_height, 1)

# Attaching a sampler switches this image's descriptor type from a
# storage image (`image2D`, raw imageLoad/imageStore) to a combined
# image sampler (`sampler2D`, hardware-filtered `texture()` reads).
# create_sampler() defaults to linear filtering with clamp-to-edge
# addressing.
input_image.create_sampler()

# A larger 16x16 output image that we fill by *sampling* (not just
# copying) the small input image, letting the GPU's bilinear filter
# smoothly interpolate between input texels.
out_width, out_height = 16, 16
output_data = np.zeros((out_height, out_width), dtype=np.float32)
output_image = mgr.image(output_data, out_width, out_height, 1)

params = [input_image, output_image]

shader = compile_source(
"""
#version 450

layout (local_size_x = 1, local_size_y = 1) in;

layout(binding = 0) uniform sampler2D inputImg;
layout(binding = 1, r32f) writeonly uniform image2D outputImg;

void main() {
ivec2 outCoord = ivec2(gl_GlobalInvocationID.xy);
ivec2 outSize = imageSize(outputImg);
vec2 uv = (vec2(outCoord) + 0.5) / vec2(outSize);
float value = texture(inputImg, uv).r;
imageStore(outputImg, outCoord, vec4(value, 0.0, 0.0, 0.0));
}
"""
)

algo = mgr.algorithm(params, shader, (out_width, out_height, 1))

(
mgr.sequence()
.record(kp.OpSyncDevice(params))
.record(kp.OpAlgoDispatch(algo))
.record(kp.OpSyncLocal(params))
.eval()
)

print(
f"Upsampled output ({out_width}x{out_height}) sampled from a "
f"{in_width}x{in_height} source image:\n"
)
print(np.round(output_image.data().reshape(out_height, out_width), 2))


if __name__ == "__main__":
main()
28 changes: 27 additions & 1 deletion python/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,33 @@ PYBIND11_MODULE(kp, m)
&kp::Memory::dataType),
DOC(kp, Memory, dataType))
.def("is_init", &kp::Image::isInit, DOC(kp, Image, isInit))
.def("destroy", &kp::Image::destroy, DOC(kp, Image, destroy));
.def("destroy", &kp::Image::destroy, DOC(kp, Image, destroy))
.def(
"create_sampler",
[](kp::Image& self, bool linear, bool repeat) {
vk::SamplerCreateInfo samplerInfo =
kp::Image::defaultSamplerCreateInfo();
vk::Filter filter =
linear ? vk::Filter::eLinear : vk::Filter::eNearest;
samplerInfo.magFilter = filter;
samplerInfo.minFilter = filter;
vk::SamplerAddressMode addressMode =
repeat ? vk::SamplerAddressMode::eRepeat
: vk::SamplerAddressMode::eClampToEdge;
samplerInfo.addressModeU = addressMode;
samplerInfo.addressModeV = addressMode;
samplerInfo.addressModeW = addressMode;
self.createSampler(samplerInfo);
},
"Attaches a Vulkan sampler to this image so it can be bound as a "
"combined image sampler (`sampler2D` in GLSL) instead of a storage "
"image (`image2D`), enabling hardware filtering/interpolation.",
py::arg("linear") = true,
py::arg("repeat") = false)
.def("has_sampler",
&kp::Image::hasSampler,
"Returns whether create_sampler() has been called on this "
"image.");

py::class_<kp::Sequence, std::shared_ptr<kp::Sequence>>(m, "Sequence")
.def(
Expand Down
25 changes: 7 additions & 18 deletions src/Algorithm.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
#include <fstream>
#include <map>

#include "kompute/Algorithm.hpp"
#include "kompute/Image.hpp"
Expand Down Expand Up @@ -128,35 +129,23 @@ Algorithm::destroy()
void
Algorithm::createParameters()
{
uint32_t numImages = 0;
uint32_t numTensors = 0;

KP_LOG_DEBUG("Kompute Algorithm createParameters started");

std::map<vk::DescriptorType, uint32_t> descriptorTypeCounts;

for (const std::shared_ptr<Memory>& mem : this->mMemObjects) {
if (mem->getDescriptorType() == vk::DescriptorType::eStorageImage) {
numImages++;
} else {
numTensors++;
}
descriptorTypeCounts[mem->getDescriptorType()]++;
}

std::vector<vk::DescriptorPoolSize> descriptorPoolSizes;

if (numTensors > 0) {
for (const auto& descriptorTypeCount : descriptorTypeCounts) {
descriptorPoolSizes.push_back(vk::DescriptorPoolSize(
vk::DescriptorType::eStorageBuffer,
static_cast<uint32_t>(numTensors) // Descriptor count
descriptorTypeCount.first,
descriptorTypeCount.second // Descriptor count
));
}

if (numImages > 0) {
descriptorPoolSizes.push_back(vk::DescriptorPoolSize(
vk::DescriptorType::eStorageImage,
static_cast<uint32_t>(numImages) // Descriptor count
));
};

vk::DescriptorPoolCreateInfo descriptorPoolInfo(
vk::DescriptorPoolCreateFlags(),
1, // Max sets
Expand Down
Loading
Loading