Jump to content

klepto2

Developers
  • Posts

    857
  • Joined

  • Last visited

Everything posted by klepto2

  1. yes, 128 is a minimum, but most amd hardware still uses this minimum, while most nvidia cards use a limit of 256: https://vulkan.gpuinfo.org/displaydevicelimit.php?name=maxPushConstantsSize&platform=all
  2. While experimenting with the posteffect system i found some smaller things which would be nice to have: 1. More possible paramters: Currently it is only possible to pass (at least if i look into the shader files) 8 different float parameters to the shader. this might be enough for most simple cases, but for more complex scenarios this might be a show-stopping limitation. eg: 8 floats means just 2 vec4 , which means just 2 colors may be passed or 2 rgb colors with 2 additional parameters. [Disclaimer] I know that you already use the maximum supported sizes (damn you AMD * ) but maybe there is another way for this (Registering uniform buffers for the rescue?). 2. currently it is only possible to create additional textures with a multiplier of the current framebuffer size, i would suggest to add the possibility to add an absolute size as well. Some effects might need some smaller textures like 256*256 or 32*32 to calculate additional data. 3. Make some passes one time only, with the possibility to mark them as dirty from the api. Explanation: Lets say we want to have simple noise distortion pfx which needs a noise texture generated (with a size of 256*256 see pt. 2) now in pass 1 you would generate the noise and use it in pass 2. Currently the first pass is executed everytime, even it is only needed once, but it should be only regenerated, when eg: the seed has changed. * AMD allows just 128 bytes for push-constants while NVIDIA has changed to 256 on more recent cards.
  3. klepto2

    pfx_pow.png

    A small Posteffect experiment with a simple scattering algoithm (not the precomputed one, but o'neils) The detail power lies in the posteffect / shader system of Ultraengine. In Leadwerks you needed to create a lua script and pass all needed data to your shader. In this case: In leadwerks i would have to iterate over all lights, calculate the vector and last pass it to the shader. if i want to have multiple lights to effect the shader i have 2 options: execute the pfx for each light source once ( with a last step to scale the colors down) or to create a limited array with the vector data for all lights. In UltraEngine this is all not needed as you have access to all engine data within the shader at nearly every time. Here i am just iterating through all directional lights inside the shader, no need of counters or vectors passed, i get instant access to the direction, color and everything else. That makes it real fun to build and write shaders.
  4. Since the Collision/Hole Update terrain throws a validation error: Validation Error: [ UNASSIGNED-CoreValidation-Shader-InputNotProduced ] Object 0: handle = 0x8168780000000092, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x23e43bb7 | fragment shader consumes input location 0.0 which is not written by vertex shader
  5. With the latest Version, the terrainsystem doesn't use the tessellation pipeline when you set something like this: camera->SetTessellation(12.0); other geometry works fine.
  6. Using this sample: https://www.ultraengine.com/learn/Terrain_LoadHeightmap?lang=cpp The result is just a flat terrain, the log states that the pixmap is loaded, but remote or local files are not modifying the terrain.
  7. When using this sample: https://www.ultraengine.com/learn/Terrain_SetMaterial?lang=cpp You can see that the Pick is not always working as it was used to be.
  8. I am currently working on a post processing effect and when i try to read the normal map i get this: It seems that the normalbuffer isn't defined in the areas where the terrain is rendered, sometimes it looks like this, sometimes when moving the camera the terrain is rendered just black. vec3 n = textureLod(texture2DSampler[PostEffectTextureID1], uv, 0).rgb; outColor = vec4(n,1.0);
  9. From what i remember the only limitations which are set within the shaders are the different textures types: Limits.glsl: #define MAX_TEXTURES_2D 512 #define MAX_TEXTURES_CUBE 128 #define MAX_TEXTURES_SHADOW 64 #define MAX_TEXTURES_2D_INTEGER 32 #define MAX_TEXTURES_2D_UINTEGER 32 #define MAX_TEXTURES_CUBE_SHADOW 128 #define MAX_VOLUME_TEXTURES 128 #define MAX_TEXTURES_STORAGE_2D 32 #define MAX_TEXTURES_STORAGE_3D 128 #define MAX_TEXTURES_STORAGE_CUBE 128 #define MAX_TEXTURES_2DMS 32 #define MAX_IMAGES_2D MAX_TEXTURES_2D #define MAX_IMAGES_3D 128 #define MAX_IMAGES_CUBE 128 Everything else should just be limited by the available memory. You can also alter this file to your needs, but you need to recompile the shaders when you do so. Which will be complicated if you rely on the terrain shaders as they are currently not available via source.
  10. This is the hacky way without direct header access, but which needs to be updated when the class structure changes: struct PublicMaterial : public Asset { // copy everything from Material.h Material class here // remove the access modifier // remove all methods/constructors and friend functions }; // Call this in your loop auto refmat = (*sample_material); auto mat = reinterpret_cast<PublicMaterial*>(&refmat); auto materialId = mat->rendermaterial->id;
  11. What i have found, is that the IDs are stored in the Render... classes, RenderMaterial, RenderTexture, etc. . These are all stored in private properties. There is a hackish way to access them (creating a separate class and modifying the original headers) but with every update this will be overwritten. I would like to add also Texture ID access, which might be useful for PostEffects. Maybe some static class extensions for the IDSystem like IDSystem::GetMaterialID(mat) IDSystem::GetTextureID(tex) ...
  12. Just forgot for those who will just take a look how the shader creation is done: /// <summary> /// Sample Buffer structure /// The Padding is needed because in vulkan the buffer passed to a shader needs to have an alignment of 16. /// </summary> struct SampleComputeParameters { float size; float padding1; float padding2; float padding3; Vec4 color; }; SampleComputeParameters sampleParameters; sampleParameters.size = 64.0; sampleParameters.color = Vec4(1.0, 0.0, 0.0, 1.0); // Create a first computeshader for uniform buffer usage // Normally you will use uniform buffers for data which is not changing, this is just for showcase // and shows that the data can still be updated at runtime auto sampleComputePipeLine_Unifom = ComputeShader::Create("Shaders/Compute/simple_test.comp.spv"); auto targetTexture_uniform = CreateTexture(TEXTURE_2D, 512, 512, TEXTURE_RGBA32, {}, 1, TEXTURE_STORAGE, TEXTUREFILTER_LINEAR); // Now we define the descriptor layout, the binding is resolved by the order in which the items are added sampleComputePipeLine_Unifom->AddTargetImage(targetTexture_uniform); // Seting up a target image --> layout 0 sampleComputePipeLine_Unifom->AddUniformBuffer(&sampleParameters, sizeof(SampleComputeParameters), false); // Seting up a uniform bufffer --> layout 1 // Create a first computeshader for push constant usage // This is the better way to pass dynamic data auto sampleComputePipeLine_Push = ComputeShader::Create("Shaders/Compute/simple_test_push.comp.spv"); auto targetTexture_push= CreateTexture(TEXTURE_2D, 512, 512, TEXTURE_RGBA32, {}, 1, TEXTURE_STORAGE, TEXTUREFILTER_LINEAR); sampleComputePipeLine_Push->AddTargetImage(targetTexture_push); sampleComputePipeLine_Push->SetupPushConstant(sizeof(SampleComputeParameters)); // Currently used to initalize the pipeline, may change in the future // For demonstration the push based shader is executed continously // The push-constant data is passed here sampleComputePipeLine_Push->BeginDispatch(world, targetTexture_uniform->GetSize().x / 16.0, targetTexture_uniform->GetSize().y / 16.0, 1, false, ComputeHook::TRANSFER, &sampleParameters, sizeof(SampleComputeParameters)); And this is how the push-shader code looks: #version 450 #extension GL_GOOGLE_include_directive : enable #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout (local_size_x = 16, local_size_y = 16) in; layout (set = 0, binding = 0, rgba32f) uniform image2D resultImage; layout (push_constant) uniform Contants { float size; vec4 color; } params; void main() { vec2 tex_coords = floor(vec2(gl_GlobalInvocationID.xy) / params.size); float mask = mod(tex_coords.x + mod(tex_coords.y, 2.0), 2.0); imageStore(resultImage,ivec2(gl_GlobalInvocationID.xy) , mask * params.color); }
  13. Ok, Here it is: https://github.com/klepto2/UltraComputeShaderSample A first version of my not much cleaned up ComputeShader implementation. It contains a small sample demonstrating the usage of uniform buffers and push constants. Note: some things are missing: You can currently only write to textures, not to uniform buffers. Also, I plan to make the Descriptor layout creation more vulkan based and reusable, the shader modules will be reusable as well for multiple shaders and you can choose the entry point which should be used.
  14. Try it like this: class NewWidget : public Widget { protected: virtual void Draw(const int x, const int y, const int width, const int height) { if (blocks.size() < 4) { for (int i = 0; i < 4; i++) { auto px = LoadPixmap("https://raw.githubusercontent.com/UltraEngine/Documentation/master/Assets/Materials/Ground/dirt01.dds"); int block = AddBlock(px, iVec2(i * 64, 0), Vec4(1)); blocks[block].size = iVec2(64, 64); } } } public: static shared_ptr<NewWidget> create(const int x, const int y, const int width, const int height, shared_ptr<Widget> parent, const int style = 0) { struct Struct : public NewWidget { }; auto instance = std::make_shared<Struct>(); instance->Initialize("", x, y, width, height, parent, style); return instance; } NewWidget() { } };
  15. Hi, sry that i wasn't here lately, i will check my compute-shader implemntation with the latest version and will provide a small sample together with my implementation. The idea behind the callbacks is, that josh is maintaining all shader and texture code optimized for his internal rendering code (which makes absolutely sense). So in order to get access to the VulkanInstance etc. you hook into the TRANSFER or RENDER Hook (due to the architecture they are not initialized at the beginning, but on the fly, when the first vulkan code is used) and in this hooks you need to setup your shader pipeline with vulkan api yourself (you can load the shaderModule with the ultraengine command and use that as a base). While i am preparing the sample, here is some pseudo code: class ComputeDispatchInfo : public Object { public: shared_ptr<ComputeDispatchInfo> Self; shared_ptr<ComputeShader> ComputeShader; int Tx; int Ty; int Tz; shared_ptr<World> World; void* pushConstants = nullptr; size_t pushConstantsSize = 0; int pushConstantsOffset = 0; ComputeHook hook = ComputeHook::RENDER; int callCount = 0; }; // RENDER/TRANSFER HOOK void BeginComputeShaderDispatch(const UltraEngine::Render::VkRenderer& renderer, shared_ptr<Object> extra) { auto info = extra->As<ComputeDispatchInfo>(); if (info != nullptr) { info->ComputeShader->Dispatch(renderer.commandbuffer, info->Tx, info->Ty, info->Tz, info->pushConstants, info->pushConstantsSize, info->pushConstantsOffset); } } void ComputeShader::init(VkDevice device) { if (!_initialized) { _computePipeLine = make_shared<ComputePipeline>(); initLayout(device); VkComputePipelineCreateInfo info = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; info.stage.module = _shaderModule->GetHandle(); info.stage.pName = "main"; info.layout = _computePipeLine->pipelineLayout; VK_CHECK_RESULT(vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &info, nullptr, &_computePipeLine->pipeline)); initLayoutData(device); _initialized = true; } } void ComputeShader::BeginDispatch(shared_ptr<World> world, int tx, int ty, int tz, bool oneTime, ComputeHook hook, void* pushData, size_t pushDataSize, int pushDataOffset) { auto info = make_shared<ComputeDispatchInfo>(); info->ComputeShader = this->Self()->As<ComputeShader>(); info->Tx = tx; info->Ty = ty; info->Tz = tz; info->World = world; info->Self = info; info->pushConstants = pushData; info->pushConstantsSize = pushDataSize; info->pushConstantsOffset = pushDataOffset; info->hook = hook; if (ComputeShader::DescriptorPool == nullptr) { ComputeShader::DescriptorPool = make_shared<ComputeDescriptorPool>(world); } switch (hook) { case ComputeHook::RENDER: world->AddHook(HookID::HOOKID_RENDER, BeginComputeShaderDispatch, info, !oneTime); break; case ComputeHook::TRANSFER: world->AddHook(HookID::HOOKID_TRANSFER, BeginComputeShaderDispatch, info, !oneTime); break; } } void ComputeShader::Dispatch(VkCommandBuffer cBuffer, int tx, int ty, int tz, void* pushData, size_t pushDataSize, int pushDataOffset) { auto manager = UltraEngine::Core::GameEngine::Get()->renderingthreadmanager; VkDevice device = manager->device->device; _timestampQuery->Init(manager->device->physicaldevice, manager->device->device); _timestampQuery->Reset(cBuffer); //just for testing vector<VkImageMemoryBarrier> barriers; bool barrierActive = false; bool isValid = true; auto path = _shaderModule->GetPath(); for (int index = 0; index < _bufferData.size(); index++) { if (_bufferData[index]->Texture != nullptr && _bufferData[index]->IsWrite) { /*auto rt = TextureMemberAccessor::GetRenderTexture(_bufferData[index]->Texture); for (int miplevel = 0; miplevel < rt->miplevels; miplevel++) { auto layout = rt->GetLayout(miplevel); if (layout == VK_IMAGE_LAYOUT_UNDEFINED) { return; } }*/ u_int layercount = 1; if (_bufferData[index]->Texture->GetType() == TEXTURE_CUBE) { layercount = 6; } vks::tools::setImageLayout(cBuffer, _bufferData[index]->Texture->GetImage(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, { VK_IMAGE_ASPECT_COLOR_BIT, (u_int)_bufferData[index]->Texture->CountMipmaps()-1,VK_REMAINING_MIP_LEVELS, 0, layercount },VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); vks::tools::insertImageMemoryBarrier(cBuffer, _bufferData[index]->Texture->GetImage(), 0, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, { VK_IMAGE_ASPECT_COLOR_BIT, (u_int)_bufferData[index]->Texture->CountMipmaps() - 1,VK_REMAINING_MIP_LEVELS, 0, layercount }); //VkImageMemoryBarrier imageMemoryBarrier = {}; //u_int layercount = 1; //if (_bufferData[index]->Texture->GetType() == TEXTURE_CUBE) //{ // layercount = 6; //} //imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; //imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; //imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; //imageMemoryBarrier.image = _bufferData[index]->Texture->GetImage(); //imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, (u_int)_bufferData[index]->Texture->CountMipmaps()-1,VK_REMAINING_MIP_LEVELS, 0, layercount}; //// Acquire barrier for compute queue //imageMemoryBarrier.srcAccessMask = 0; //imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; //imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; //imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; //vkCmdPipelineBarrier( // cBuffer, // VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, // 0, // 0, nullptr, // 0, nullptr, // 1, &imageMemoryBarrier); //barriers.push_back(imageMemoryBarrier); //barrierActive = true; break; } } //initializes the layout and Writedescriptors init(device); //updates the uniform buffer data when needed updateData(device); vkCmdBindPipeline(cBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, _computePipeLine->pipeline); // Bind descriptor set. vkCmdBindDescriptorSets(cBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, _computePipeLine->pipelineLayout, 0, 1, &_computePipeLine->descriptorSet, 0, nullptr); // Bind the compute pipeline. if (pushData != nullptr) { vkCmdPushConstants(cBuffer, _computePipeLine->pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, pushDataOffset, pushDataSize, pushData); } _timestampQuery->write(cBuffer, 0); // Dispatch compute job. vkCmdDispatch(cBuffer, tx, ty, tz); _timestampQuery->write(cBuffer, 1); if (barrierActive) { for (int i = 0; i < barriers.size(); i++) { // Release barrier from compute queue barriers[i].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barriers[i].dstAccessMask = 0; barriers[i].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barriers[i].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; vkCmdPipelineBarrier( cBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &barriers[i]); } } } Note: There is much more needed as you need to manage you own DescriptorSets VKImages etc. The idea is that the non ultraengine extensions are nearly completely unaware of the internal rendering,
  16. I am using a small helper method to check if the interface is native or not (maybe something like this could make it into the Widget class) and to decide if i could use textures or must use pixmaps: bool IsNativeInterface(shared_ptr<Interface> i) { return i->GetWindow() != NULL; } If the interface is not native and you want or need to interact with the window you need to get it with ActiveWindow() and not use ui->GetWindow().
  17. I like the idea, but i doubt this will be done, as it would be ingame only. The native gui interface relies on the native drawing methods provided by the os. This is why can't use the texture block when you add an interface to a window. An alternative would be some kind of tiled pixmap block for this purpose, with that you can define the margins needed and the block itself creates this kind of behaviour. but it would be hard to animate (at least performance wise) as we can't use 3d textures. I have an idea which i will try tomorrow
  18. Depends. If the update is immediately, I would go with Refresh else I would use invalidate. The problem with this is that it more or less is only valid for the ui system in all other cases the pixmap works well without it. So maybe it should be named to something like RefreshUi?
  19. That’s what I’m currently doing, but to have the pixmap update correctly in the ui, I needed to cheat a bit, to get access to the getbitmap method and update it myself. A method which would trigger the bitmap update would be useful. Or unify the getbitmap method on all so, withjust diefferent return types.
  20. The rendering is done by a combination of gdi+ and the basic win32 api. I will send you some code tomorrow. But the keyword is PrintWindow() and getdibits().
  21. Please think about some kind of setpixels(void* buffer) which uses the update mechanic, writepixel might be much to slow for a lot of scenarios.
  22. The Airspace problem is a description for mixing different GUI technologies. In this case, the scintilla control uses natively gdi+ and its own window class and HWND. this window is rendered by gdi+ while the rest of the ui is rendered by UltraEngine. So to show the scintilla control, it must always been on top of the Ultra Engine-Widgets, which means that the z-Index or order doesn't matter and in the gif you can see that the red overlay is clipped by the scintilla control, while with the redirection to the pixmap this is no problem, because i render the pixmap natively in UltraEngine and the original Scintilla Window is hidden. The term mostly comes from WPF and WinForms: https://adityaswami89.wordpress.com/2014/02/24/airspace-problem-wpf/
  23. In this gif you can see a small app with 3 tabs. The first tab renderes the scintilla control normally as an extra window above the actual Ultra Engine-Window. The second tab redirects the rendering to a pixmap and uses this to display the content in UltraEngine. The second appoach supprts full integration into the UltraEngine-UI with blending and overlays while the first has many drawbacks (airspace problem, no alpha blending) the 3rd is reserved for ingame ui mode, when the ui is rendered with vulkan. Then the control is redirected to a texture and this is used.
  24. #define ALLOCEVENTID EventId(10001 + __COUNTER__) #define RegisterEventEnum(T) \ bool operator==(const T& t, const EventId& g) { return static_cast<EventId>(t) == g; } \ bool operator==(const EventId& g, const T& t) { return static_cast<EventId>(t) == g; } \ \ bool operator!=(const T& t, const EventId& g) { return static_cast<EventId>(t) != g; } \ bool operator!=(const EventId& g, const T& t) { return static_cast<EventId>(t) != g; } \ \ EventId& operator<<=(EventId& g, T t) { g = static_cast<EventId>(t); return g; } \ T& operator<<=(T& t, EventId g) { t = static_cast<T>(g); return t; }; \ enum CustomEvent { TEST_1 = ALLOCEVENTID, TEST_2 = ALLOCEVENTID, }; RegisterEventEnum(CustomEvent); Try this. [Edit] Forget it, it is not working. switch statements work out of the box, but functions will not work without proper casting
  25. #include "UltraEngine.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 800, 600, displays[0], WINDOW_TITLEBAR | WINDOW_RESIZABLE); //Create User Interface auto ui = CreateInterface(window); auto sz = ui->root->ClientSize(); //Create widget auto panel = CreatePanel(50, 50, sz.x - 100, sz.y - 100, ui->root); panel->SetColor(0, 0, 0, 1); panel->SetLayout(1, 1, 1, 1); auto pixmap = CreatePixmap(256, 256); pixmap->Fill(0xFF0000FF); panel->SetPixmap(pixmap); auto btnChangeColor1 = CreateButton("Change Color without fill", 50, 10, 200, 30, ui->root); auto btnChangeColor2 = CreateButton("Change Color with fill", 260, 10, 200, 30, ui->root); while (true) { const Event ev = WaitEvent(); switch (ev.id) { case EVENT_WIDGETACTION: { if (ev.source == btnChangeColor1) { int color = Rgba(Random(255), Random(255), Random(255), 255); for (int x = 0; x < pixmap->size.width; x++) for (int y = 0; y < pixmap->size.height; y++) pixmap->WritePixel(x, y, color); } else if (ev.source == btnChangeColor2) { pixmap->Fill(Rgba(Random(255), Random(255), Random(255), 255)); } panel->Paint(); break; } case EVENT_WINDOWCLOSE: return 0; break; } } return 0; } I am currently experimenting with redirecting the scintilla rendering to a pixmap and found a small bug. The pixmap is only updated, in this case on the panel, but also when using just the WidgetBlock, when you use the Fill method. any other pixel manipulation is not working when using pixmaps in the ui. I assume, that it might have to do with the underlying Bitmap object is not updated. A nice way would be to have something to mark the pixmap as dirty, as i need to use memcpy for performance reasons and then the pixmap will not know if it has chnaged or not.
×
×
  • Create New...