diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 8010718447b8e72aed8eab42c8eac3e9591986ee..4ea6cb3cbc1537ebd572e9483cc4e20acc3e46ab 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -6,4 +6,5 @@ add_subdirectory(first_scene)
 add_subdirectory(particle_simulation)
 add_subdirectory(voxelization)
 add_subdirectory(mesh_shader)
-add_subdirectory(indirect_dispatch)
+add_subdirectory(saf_r)
+add_subdirectory(indirect_dispatch)
\ No newline at end of file
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 5ef277dfedbfa823a2b6fa55c5a6303117ddaa52..0871631827b87539bbe9b0050420088e199a39af 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -101,7 +101,7 @@ int main(int argc, const char** argv) {
 
 	// since we only use one descriptor set (namely, desc set 0), directly address it
 	// recreate copies of the bindings and the handles (to check whether they are properly reused instead of actually recreated)
-	std::unordered_map<uint32_t, vkcv::DescriptorBinding> set0Bindings = firstMeshProgram.getReflectedDescriptors().at(0);
+	const vkcv::DescriptorBindings& set0Bindings = firstMeshProgram.getReflectedDescriptors().at(0);
     auto set0BindingsExplicitCopy = set0Bindings;
 
 	vkcv::DescriptorSetLayoutHandle setLayoutHandle = core.createDescriptorSetLayout(set0Bindings);
diff --git a/projects/saf_r/.gitignore b/projects/saf_r/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ff3dff30031efafa24269a9ac0ef93f64f63ded1
--- /dev/null
+++ b/projects/saf_r/.gitignore
@@ -0,0 +1 @@
+saf_r
\ No newline at end of file
diff --git a/projects/saf_r/CMakeLists.txt b/projects/saf_r/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..61ede8ae5a5cedac78ff5781aec20973854a3df7
--- /dev/null
+++ b/projects/saf_r/CMakeLists.txt
@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 3.16)
+project(saf_r)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# this should fix the execution path to load local files from the project
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+# adding source files to the project
+add_executable(saf_r
+		src/main.cpp 
+		"src/safrScene.hpp" 
+		)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(saf_r PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(saf_r PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+	# in addition to setting the output directory, the working directory has to be set
+	# by default visual studio sets the working directory to the build directory, when using the debugger
+	set_target_properties(saf_r PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(saf_r SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(saf_r vkcv vkcv_testing vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/saf_r/shaders/raytracing.comp b/projects/saf_r/shaders/raytracing.comp
new file mode 100644
index 0000000000000000000000000000000000000000..a7c6b92a646e5c2f753946f74fe7ab78aea44fe6
--- /dev/null
+++ b/projects/saf_r/shaders/raytracing.comp
@@ -0,0 +1,292 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+// defines constants
+const float pi      = 3.1415926535897932384626433832795;
+const float hitBias = 0.01;   // used to offset hits to avoid self intersection
+
+layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
+
+//structs of materials, lights, spheres and intersection for use in compute shader
+struct Material {
+    vec3 albedo;
+    vec3 diffuseColor;
+    float specularExponent;
+    float refractiveIndex;
+};
+
+struct Light{
+    vec3 position;
+    float intensity;
+};
+
+struct Sphere{
+    vec3 center;
+    float radius;
+    Material material;
+};
+
+struct Intersection{
+    bool hit;
+    vec3 pos;
+    vec3 N;
+    Material material;
+};
+
+
+//incoming light data
+layout(std430, binding = 0) coherent buffer lights{
+    Light inLights[];
+};
+
+// incoming sphere data
+layout(std430, binding = 1) coherent buffer spheres{
+    Sphere inSpheres[];
+};
+
+// output store image as swapchain input
+layout(set=0, binding = 2, rgba8) uniform image2D outImage;
+
+// incoming constants, because size of dynamic arrays cannot be computed on gpu
+layout( push_constant ) uniform constants{
+    mat4 viewToWorld;
+    int lightCount;
+    int sphereCount;
+};
+
+/*
+* safrReflect computes the new reflected or refracted ray depending on the material
+* @param vec3: raydirection vector
+* @param vec3: normalvector on which should be reflected or refracted
+* @param float: degree of refraction. In case of simple reflection it is 1.0
+* @return vec3: new ray that is the result of the reflection or refraction
+*/
+vec3 safrReflect(vec3 V, vec3 N, float refractIndex){
+    if(refractIndex != 1.0){
+        // Snell's law
+        float cosi = - max(-1.f, min(1.f, dot(V,N)));
+        float etai = 1;
+        float etat = refractIndex;
+        vec3 n = N;
+        float swap;
+        if(cosi < 0){
+            cosi = -cosi;
+            n = -N;
+            swap = etai;
+            etai = etat;
+            etat = swap;
+        }
+        float eta = etai / etat;
+        float k = 1 - eta * eta * (1 - cosi * cosi);
+        if(k < 0){
+            return vec3(0,0,0);
+        } else {
+            return V * eta + n * (eta * cosi - sqrt(k));
+        }
+    }else{
+        return reflect(V, N);
+    }
+}
+
+/*
+* the rayIntersect function checks, if a ray from the raytracer passes through the sphere, hits the sphere or passes by the the sphere
+* @param vec3: origin of ray
+* @param vec3: direction of ray
+* @param float: distance of the ray to the sphere (out because there are no references in shaders)
+* @return bool: if ray interesects sphere or not (out because there are no references in shaders)
+*/
+
+bool rayIntersect(const vec3 origin, const vec3 dir, out float t0, const int id){
+    vec3 L = inSpheres[id].center - origin;
+    float tca = dot(L, dir);
+    float d2 = dot(L, L) - tca * tca;
+    if (d2 > inSpheres[id].radius * inSpheres[id].radius){
+        return false;
+    }
+    float thc = float(sqrt(inSpheres[id].radius * inSpheres[id].radius - d2));
+    t0 = tca - thc;
+    float t1 = tca + thc;
+    if (t0 < 0) {
+        t0 = t1;
+    }
+    if (t0 < 0){
+        return false;
+    }
+    return true;
+}
+
+/*
+* sceneIntersect iterates over whole scene (over every single object) to check for intersections
+* @param vec3: Origin of the ray
+* @param vec3: direction of the ray
+* @return: Intersection struct with hit(bool) position, normal and material of sphere
+*/
+
+Intersection sceneIntersect(const vec3 rayOrigin, const vec3 rayDirection) {
+    //distance if spheres will be rendered
+    float   min_d    = 1.0 / 0.0;  // lets start with something big
+    
+    Intersection intersection;
+    intersection.hit = false;
+    
+    //go over every sphere, check if sphere is hit by ray, save if hit is near enough into intersection struct
+    for (int i = 0; i < sphereCount; i++) {
+        float d;
+        if (rayIntersect(rayOrigin, rayDirection, d, i)) {
+            
+            intersection.hit = true;
+            
+            if(d < min_d){
+                min_d = d;
+                intersection.pos        = rayOrigin + rayDirection * d;
+                intersection.N          = normalize(intersection.pos - inSpheres[i].center);
+                intersection.material   = inSpheres[i].material;
+            }
+        }
+    }
+
+    float checkerboard_dist = min_d;
+    if (abs(rayDirection.y)>1e-3)  {
+        float d = -(rayOrigin.y + 4) / rayDirection.y; // the checkerboard plane has equation y = -4
+        vec3 pt = rayOrigin + rayDirection * d;
+        if (d > 0 && abs(pt.x) < 10 && pt.z<-10 && pt.z>-30 && d < min_d) {
+            checkerboard_dist = d;
+            intersection.hit = true;
+            intersection.pos = pt;
+            intersection.N = vec3(0, 1, 0);
+            intersection.material = inSpheres[0].material;
+        }
+    }
+    return intersection;
+}
+
+/*
+* biasHitPosition computes the new hitposition with respect to the raydirection and a bias
+* @param vec3: Hit Position
+* @param vec3: direction of ray
+* @param vec3: N(ormal)
+* @return vec3: new Hit position depending on hitBias (used to offset hits to avoid self intersection)
+*/
+vec3 biasHitPosition(vec3 hitPos, vec3 rayDirection, vec3 N){
+    return hitPos + sign(dot(rayDirection, N)) * N * hitBias;
+}
+
+/*
+* computeHitLighting iterates over all lights to compute the color for every ray
+* @param Intersection: struct with all the data of the intersection
+* @param vec3: Raydirection
+* @param float: material albedo of the intersection
+* @return colour/shadows of sphere with illumination
+*/
+vec3 computeHitLighting(Intersection intersection, vec3 V, out float outReflectionThroughput){
+    
+    float lightIntensityDiffuse  = 0;
+    float lightIntensitySpecular = 0;
+
+    //iterate over every light source to compute sphere colours/shadows
+    for (int i = 0; i < lightCount; i++) {
+
+        //compute normal + distance between light and intersection
+        vec3   L = normalize(inLights[i].position - intersection.pos);
+        float  d = distance(inLights[i].position, intersection.pos);
+
+        //compute shadows
+        vec3 shadowOrigin = biasHitPosition(intersection.pos, L, intersection.N);
+        Intersection shadowIntersection = sceneIntersect(shadowOrigin, L);
+        bool isShadowed = false;
+        if(shadowIntersection.hit){
+            isShadowed = distance(shadowIntersection.pos, shadowOrigin) < d;
+        }
+        if(isShadowed){
+            continue;
+        }
+
+        lightIntensityDiffuse  += inLights[i].intensity * max(0.f, dot(L, intersection.N));
+        lightIntensitySpecular += pow(max(0.f, dot(safrReflect(V, intersection.N, intersection.material.refractiveIndex), L)), intersection.material.specularExponent) * inLights[i].intensity;
+    }
+
+    outReflectionThroughput = intersection.material.albedo[2];
+    return intersection.material.diffuseColor * lightIntensityDiffuse * intersection.material.albedo[0] + lightIntensitySpecular * intersection.material.albedo[1];
+}
+
+/*
+* castRay throws a ray out of the initial origin with respect to the initial direction checks for intersection and refelction
+* @param vec3: initial origin of ray
+* @param vec3: initial direction of ray
+* @param int: max depth o ray reflection
+* @return s
+*/
+
+vec3 castRay(const vec3 initialOrigin, const vec3 initialDirection, int max_depth) {
+    
+    vec3 skyColor = vec3(0.2, 0.7, 0.8);
+    vec3 rayOrigin    = initialOrigin;
+    vec3 rayDirection = initialDirection;
+    
+    float   reflectionThroughput    = 1;
+    vec3    color                   = vec3(0);
+
+    //iterate to max depth of reflections
+    for(int i = 0; i < max_depth; i++){
+
+        Intersection intersection = sceneIntersect(rayOrigin, rayDirection);
+
+        vec3 hitColor;
+        float hitReflectionThroughput;
+
+        if(intersection.hit){
+            hitColor = computeHitLighting(intersection, rayDirection, hitReflectionThroughput);
+        }else{
+            hitColor = skyColor;
+        }
+
+        color                   += hitColor * reflectionThroughput;
+        reflectionThroughput    *= hitReflectionThroughput;
+
+        //if there is no intersection of a ray with a sphere, break out of the loop
+        if(!intersection.hit){
+            break;
+        }
+
+        //compute new direction and origin of the reflected ray
+        rayDirection    = normalize(safrReflect(rayDirection, intersection.N, intersection.material.refractiveIndex));
+        rayOrigin       = biasHitPosition(intersection.pos, rayDirection, intersection.N);
+    }
+
+    return color;
+}
+
+/*
+* computeDirection transforms the pixel coords to worldspace coords
+* @param ivec2: pixel coordinates
+* @return vec3: world coordinates
+*/
+vec3 computeDirection(ivec2 coord){
+
+    ivec2 outImageRes   = imageSize(outImage);
+    float fovDegree     = 45;
+    float fov           = fovDegree * pi / 180;
+    
+    vec2 uv     = coord / vec2(outImageRes);
+    vec2 ndc    = 2 * uv - 1;
+    
+    float tanFovHalf    = tan(fov / 2.f);
+    float aspectRatio   = outImageRes.x / float(outImageRes.y);
+    float x             =  ndc.x * tanFovHalf * aspectRatio;
+    float y             = -ndc.y * tanFovHalf;
+    
+    vec3 directionViewSpace     = normalize(vec3(x, y, 1));
+    vec3 directionWorldSpace    = mat3(viewToWorld) * directionViewSpace;
+    return directionWorldSpace;
+}
+
+// the main function
+void main(){
+    ivec2 coord     = ivec2(gl_GlobalInvocationID.xy);
+    int max_depth   = 4;
+    vec3 direction  = computeDirection(coord);
+    vec3 cameraPos  = viewToWorld[3].xyz;
+    vec3 color      = castRay(cameraPos, direction, max_depth);
+    
+    imageStore(outImage, coord, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/saf_r/shaders/shader.frag b/projects/saf_r/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..64b45eb26b9831f6504e69882018e46120615615
--- /dev/null
+++ b/projects/saf_r/shaders/shader.frag
@@ -0,0 +1,13 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 1) in vec2 texCoord;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=1) uniform sampler    textureSampler;
+
+void main() {
+	outColor = fragColor;
+}
\ No newline at end of file
diff --git a/projects/saf_r/shaders/shader.vert b/projects/saf_r/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b6419f5e348ddeca66d1c8b0eb0a4cf32e2e80c4
--- /dev/null
+++ b/projects/saf_r/shaders/shader.vert
@@ -0,0 +1,32 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) out vec3 fragColor;
+layout(location = 1) out vec2 texCoord;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 proj;
+};
+
+void main()	{
+    vec3 positions[3] = {
+        vec3(-1, -1, -1),
+        vec3( 3, -1, -1),
+        vec3(-1, 3, -1)
+    };
+    
+    vec3 colors[3] = {
+        vec3(1, 0, 0),
+        vec3(0, 1, 0),
+        vec3(0, 0, 1)
+    };
+
+    vec4 position = mvp * vec4(positions[gl_VertexIndex], 1.0);
+	gl_Position = position;
+
+    texCoord.x = ((proj * vec4(positions[gl_VertexIndex], 1.0)).x + 1.0) * 0.5;
+    texCoord.y = ((proj * vec4(positions[gl_VertexIndex], 1.0)).y + 1.0) * 0.5;
+
+	fragColor = colors[gl_VertexIndex];
+}
\ No newline at end of file
diff --git a/projects/saf_r/src/main.cpp b/projects/saf_r/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3fef073a00f8263cc08ce17f033170d0f4031dc4
--- /dev/null
+++ b/projects/saf_r/src/main.cpp
@@ -0,0 +1,297 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <chrono>
+#include <limits>
+#include <cmath>
+#include <vector>
+#include <string.h>	// memcpy(3)
+#include "safrScene.hpp"
+
+
+void createQuadraticLightCluster(std::vector<safrScene::Light>& lights, int countPerDimension, float dimension, float height, float intensity) {
+    float distance = dimension/countPerDimension;
+
+    for(int x = 0; x <= countPerDimension; x++) {
+        for (int z = 0; z <= countPerDimension; z++) {
+            lights.push_back(safrScene::Light(glm::vec3(x * distance, height,  z * distance),
+                                              float (intensity/countPerDimension) / 10.f) // Divide by 10, because intensity is busting O.o
+                                              );
+        }
+    }
+
+}
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "SAF_R";
+
+	//window creation
+	const int windowWidth = 800;
+	const int windowHeight = 600;
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, false);
+
+	//configuring the compute Shader
+	vkcv::PassConfig computePassDefinition({});
+	vkcv::PassHandle computePass = core.createPass(computePassDefinition);
+
+	if (!computePass)
+	{
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	std::string shaderPathCompute = "shaders/raytracing.comp";
+
+	//creating the shader programs
+	vkcv::ShaderProgram safrShaderProgram;
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram computeShaderProgram{};
+
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"),
+		[&safrShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+			safrShaderProgram.addShader(shaderStage, path);
+	});
+
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"),
+		[&safrShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+			safrShaderProgram.addShader(shaderStage, path);
+	});
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		computeShaderProgram.addShader(shaderStage, path);
+	});
+
+	//create DescriptorSets (...) for every Shader
+	const vkcv::DescriptorBindings& descriptorBindings = safrShaderProgram.getReflectedDescriptors().at(0);
+	vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings);
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+	vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::DescriptorBindings& computeDescriptorBindings = computeShaderProgram.getReflectedDescriptors().at(0);
+	
+	vkcv::DescriptorSetLayoutHandle computeDescriptorSetLayout = core.createDescriptorSetLayout(computeDescriptorBindings);
+	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeDescriptorSetLayout);
+
+	const std::vector<vkcv::VertexAttachment> computeVertexAttachments = computeShaderProgram.getVertexAttachments();
+
+	std::vector<vkcv::VertexBinding> computeBindings;
+	for (size_t i = 0; i < computeVertexAttachments.size(); i++) {
+		computeBindings.push_back(vkcv::VertexBinding(i, { computeVertexAttachments[i] }));
+	}
+	const vkcv::VertexLayout computeLayout(computeBindings);
+	
+	/*
+	* create the scene
+	*/
+
+	//materials for the spheres
+	std::vector<safrScene::Material> materials;
+	safrScene::Material ivory(glm::vec4(0.6, 0.3, 0.1, 0.0), glm::vec3(0.4, 0.4, 0.3), 50., 1.0);
+	safrScene::Material red_rubber(glm::vec4(0.9, 0.1, 0.0, 0.0), glm::vec3(0.3, 0.1, 0.1), 10., 1.0);
+	safrScene::Material mirror( glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.0);
+    safrScene::Material glass( glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.5);
+
+	materials.push_back(ivory);
+	materials.push_back(red_rubber);
+	materials.push_back(mirror);
+
+	//spheres for the scene
+	std::vector<safrScene::Sphere> spheres;
+	spheres.push_back(safrScene::Sphere(glm::vec3(-3,    0,   -16), 2, ivory));
+	// spheres.push_back(safrScene::Sphere(glm::vec3(-1.0, -1.5, 12), 2, mirror));
+	spheres.push_back(safrScene::Sphere(glm::vec3(-1.0, -1.5, -12), 2, glass));
+	spheres.push_back(safrScene::Sphere(glm::vec3(  1.5, -0.5, -18), 3, red_rubber));
+	spheres.push_back(safrScene::Sphere(glm::vec3( 7,    5,   -18), 4, mirror));
+
+	//lights for the scene
+	std::vector<safrScene::Light> lights;
+	/*
+	lights.push_back(safrScene::Light(glm::vec3(-20, 20,  20), 1.5));
+	lights.push_back(safrScene::Light(glm::vec3(30,  50, -25), 1.8));
+	lights.push_back(safrScene::Light(glm::vec3(30,  20,  30), 1.7));
+    */
+    createQuadraticLightCluster(lights, 10, 2.5f, 20, 1.5f);
+
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	
+	//create Buffer for compute shader
+	vkcv::Buffer<safrScene::Light> lightsBuffer = core.createBuffer<safrScene::Light>(
+		vkcv::BufferType::STORAGE,
+		lights.size()
+	);
+	lightsBuffer.fill(lights);
+
+	vkcv::Buffer<safrScene::Sphere> sphereBuffer = core.createBuffer<safrScene::Sphere>(
+		vkcv::BufferType::STORAGE,
+		spheres.size()
+	);
+	sphereBuffer.fill(spheres);
+
+	vkcv::DescriptorWrites computeWrites;
+	computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,lightsBuffer.getHandle()),
+                                          vkcv::BufferDescriptorWrite(1,sphereBuffer.getHandle())};
+    core.writeDescriptorSet(computeDescriptorSet, computeWrites);
+
+
+	const auto& context = core.getContext();
+
+	auto safrIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	uint16_t indices[3] = { 0, 1, 2 };
+	safrIndexBuffer.fill(&indices[0], sizeof(indices));
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchain(windowHandle).getFormat());
+
+	vkcv::PassConfig safrPassDefinition({ present_color_attachment });
+	vkcv::PassHandle safrPass = core.createPass(safrPassDefinition);
+
+	if (!safrPass)
+	{
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	//create the render pipeline + compute pipeline
+	const vkcv::GraphicsPipelineConfig safrPipelineDefinition{
+			safrShaderProgram,
+			(uint32_t)windowWidth,
+			(uint32_t)windowHeight,
+			safrPass,
+			{},
+			{ core.getDescriptorSetLayout(descriptorSetLayout).vulkanHandle },
+			false
+	};
+
+	vkcv::GraphicsPipelineHandle safrPipeline = core.createGraphicsPipeline(safrPipelineDefinition);
+
+	const vkcv::ComputePipelineConfig computePipelineConfig{
+			computeShaderProgram,
+			{core.getDescriptorSetLayout(computeDescriptorSetLayout).vulkanHandle}
+	};
+
+	vkcv::ComputePipelineHandle computePipeline = core.createComputePipeline(computePipelineConfig);
+
+	if (!safrPipeline || !computePipeline)
+	{
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	auto start = std::chrono::system_clock::now();
+
+	const vkcv::Mesh renderMesh({}, safrIndexBuffer.getVulkanHandle(), 3);
+	vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+	vkcv::DrawcallInfo drawcall(renderMesh, { descriptorUsage }, 1);
+
+	//create the camera
+	vkcv::camera::CameraManager cameraManager(core.getWindow(windowHandle));
+	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, 2));
+	cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
+	cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
+
+	float time = 0;
+	
+	while (vkcv::Window::hasOpenWindow())
+	{
+		vkcv::Window::pollEvents();
+
+		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
+		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
+			continue;
+		}
+
+		//configure timer
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		start = end;
+		
+		time += 0.000001f * static_cast<float>(deltatime.count());
+		
+		//adjust light position
+		/*
+		639a53157e7d3936caf7c3e40379159cbcf4c89e
+		lights[0].position.x += std::cos(time * 3.0f) * 2.5f;
+		lights[1].position.z += std::cos(time * 2.5f) * 3.0f;
+		lights[2].position.y += std::cos(time * 1.5f) * 4.0f;
+		lightsBuffer.fill(lights);
+		*/
+
+		spheres[0].center.y += std::cos(time * 0.5f * 3.141f) * 0.25f;
+		spheres[1].center.x += std::cos(time * 2.f) * 0.25f;
+		spheres[1].center.z += std::cos(time * 2.f + 0.5f * 3.141f) * 0.25f;
+        sphereBuffer.fill(spheres);
+
+		//update camera
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+		glm::mat4 proj = cameraManager.getActiveCamera().getProjection();
+
+		//create pushconstants for render
+		vkcv::PushConstants pushConstants(sizeof(glm::mat4) * 2);
+		pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ mvp, proj });
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		//configure the outImage for compute shader (render into the swapchain image)
+        computeWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, swapchainInput)};
+        core.writeDescriptorSet(computeDescriptorSet, computeWrites);
+        core.prepareImageForStorage (cmdStream, swapchainInput);
+
+		//fill pushconstants for compute shader
+        struct RaytracingPushConstantData {
+            glm::mat4 viewToWorld;
+            int32_t lightCount;
+            int32_t sphereCount;
+        };
+
+        RaytracingPushConstantData raytracingPushData;
+        raytracingPushData.lightCount   = lights.size();
+        raytracingPushData.sphereCount  = spheres.size();
+        raytracingPushData.viewToWorld  = glm::inverse(cameraManager.getActiveCamera().getView());
+
+        vkcv::PushConstants pushConstantsCompute(sizeof(RaytracingPushConstantData));
+        pushConstantsCompute.appendDrawcall(raytracingPushData);
+
+		//dispatch compute shader
+		uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil( swapchainWidth/16.f)),
+                                            static_cast<uint32_t> (std::ceil(swapchainHeight/16.f)),
+                                            1 }; // Anzahl workgroups
+		core.recordComputeDispatchToCmdStream(cmdStream,
+			computePipeline,
+			computeDispatchCount,
+			{ vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet).vulkanHandle) },
+			pushConstantsCompute);
+
+		core.recordBufferMemoryBarrier(cmdStream, lightsBuffer.getHandle());
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		core.endFrame(windowHandle);
+	}
+	return 0;
+}
diff --git a/projects/saf_r/src/safrScene.hpp b/projects/saf_r/src/safrScene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..33a298f82121971021d1912e6c1205e9c48a49f0
--- /dev/null
+++ b/projects/saf_r/src/safrScene.hpp
@@ -0,0 +1,51 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <chrono>
+#include <limits>
+#include <cmath>
+#include <vector>
+#include <string.h>	// memcpy(3)
+
+class safrScene {
+
+public:
+
+	/*
+	* Light struct with a position and intensity of the light source
+	*/
+	struct Light {
+		Light(const glm::vec3& p, const float& i) : position(p), intensity(i) {}
+		glm::vec3 position;
+		float intensity;
+	};
+
+	/*
+	* Material struct with defuse color, albedo and specular component
+	*/
+	struct Material {
+		Material(const glm::vec4& a, const glm::vec3& color, const float& spec, const float& r) : albedo(a), diffuse_color(color), specular_exponent(spec), refractive_index(r) {}
+		Material() : refractive_index(1), albedo(1, 0, 0, 0), diffuse_color(), specular_exponent() {}
+        glm::vec4 albedo;
+        alignas(16) glm::vec3 diffuse_color;
+        float specular_exponent;
+		float refractive_index;
+	};
+
+	/*
+	* the sphere is defined by it's center, the radius and the material
+	*/
+	struct Sphere {
+		glm::vec3 center;
+		float radius;
+		Material material;
+
+		Sphere(const glm::vec3& c, const float& r, const Material& m) : center(c), radius(r), material(m) {}
+
+	};
+
+	
+};
\ No newline at end of file
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index b8d50c21c288735fa621d7d158493e0eb4fab4b0..99e6df6f2cf605e7fe4081b6aeb02950c9155318 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -79,7 +79,7 @@ namespace vkcv
         }
 
         //create the descriptor set's layout from the binding data gathered above
-        vk::DescriptorSetLayout vulkanHandle = VK_NULL_HANDLE;
+        vk::DescriptorSetLayout vulkanHandle = nullptr;
         vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindingsVector);
         auto result = m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &vulkanHandle);
         if (result != vk::Result::eSuccess) {
@@ -96,7 +96,7 @@ namespace vkcv
     {
         //create and allocate the set based on the layout provided
         DescriptorSetLayout setLayout = m_DescriptorSetLayouts[setLayoutHandle.getId()];
-        vk::DescriptorSet vulkanHandle = VK_NULL_HANDLE;
+        vk::DescriptorSet vulkanHandle = nullptr;
         vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle);
         auto result = m_Device.allocateDescriptorSets(&allocInfo, &vulkanHandle);
         if(result != vk::Result::eSuccess)