diff --git a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp index 7186920a8..03bb19591 100644 --- a/qrenderdoc/Windows/ConstantBufferPreviewer.cpp +++ b/qrenderdoc/Windows/ConstantBufferPreviewer.cpp @@ -104,7 +104,7 @@ void ConstantBufferPreviewer::OnEventChanged(uint32_t eventId) { BoundCBuffer cb = m_Ctx.CurPipelineState().GetConstantBuffer(m_stage, m_slot, m_arrayIdx); m_cbuffer = cb.resourceId; - uint64_t offs = cb.byteOffset; + uint64_t offset = cb.byteOffset; uint64_t size = cb.byteSize; ResourceId prevShader = m_shader; @@ -135,8 +135,8 @@ void ConstantBufferPreviewer::OnEventChanged(uint32_t eventId) if(!m_formatOverride.type.members.empty()) { - m_Ctx.Replay().AsyncInvoke([this, offs, size, wasEmpty](IReplayController *r) { - bytebuf data = r->GetBufferData(m_cbuffer, offs, size); + m_Ctx.Replay().AsyncInvoke([this, offset, size, wasEmpty](IReplayController *r) { + bytebuf data = r->GetBufferData(m_cbuffer, offset, size); rdcarray vars = applyFormatOverride(data); GUIInvoke::call(this, [this, vars, wasEmpty] { RDTreeViewExpansionState state; @@ -153,35 +153,36 @@ void ConstantBufferPreviewer::OnEventChanged(uint32_t eventId) } else { - m_Ctx.Replay().AsyncInvoke([this, prevShader, entryPoint, offs, wasEmpty](IReplayController *r) { - rdcarray vars = r->GetCBufferVariableContents( - m_pipe, m_shader, entryPoint.toUtf8().data(), m_slot, m_cbuffer, offs); - GUIInvoke::call(this, [this, prevShader, vars, wasEmpty] { + m_Ctx.Replay().AsyncInvoke( + [this, prevShader, entryPoint, offset, size, wasEmpty](IReplayController *r) { + rdcarray vars = r->GetCBufferVariableContents( + m_pipe, m_shader, entryPoint.toUtf8().data(), m_slot, m_cbuffer, offset, size); + GUIInvoke::call(this, [this, prevShader, vars, wasEmpty] { - RDTreeViewExpansionState &prevShaderExpansionState = - ui->variables->getInternalExpansion(qHash(ToQStr(prevShader))); + RDTreeViewExpansionState &prevShaderExpansionState = + ui->variables->getInternalExpansion(qHash(ToQStr(prevShader))); - // stage, slot, and array index are all invariant across a given ConstantBufferPreviewer - // instance. We only need to use the actual bound shader as a key. - ui->variables->saveExpansion(prevShaderExpansionState, 0); + // stage, slot, and array index are all invariant across a given ConstantBufferPreviewer + // instance. We only need to use the actual bound shader as a key. + ui->variables->saveExpansion(prevShaderExpansionState, 0); - setVariables(vars); - if(wasEmpty) - { - for(int i = 0; i < 3; i++) - ui->variables->resizeColumnToContents(i); - } + setVariables(vars); + if(wasEmpty) + { + for(int i = 0; i < 3; i++) + ui->variables->resizeColumnToContents(i); + } - // if we have saved expansion state for the new shader, apply it, otherwise apply the - // previous one to get any overlap (e.g. two different shaders with very similar or - // identical constants) - if(ui->variables->hasInternalExpansion(qHash(ToQStr(m_shader)))) - ui->variables->applyExpansion( - ui->variables->getInternalExpansion(qHash(ToQStr(m_shader))), 0); - else - ui->variables->applyExpansion(prevShaderExpansionState, 0); - }); - }); + // if we have saved expansion state for the new shader, apply it, otherwise apply the + // previous one to get any overlap (e.g. two different shaders with very similar or + // identical constants) + if(ui->variables->hasInternalExpansion(qHash(ToQStr(m_shader)))) + ui->variables->applyExpansion( + ui->variables->getInternalExpansion(qHash(ToQStr(m_shader))), 0); + else + ui->variables->applyExpansion(prevShaderExpansionState, 0); + }); + }); } } diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 690bd19b1..84572145d 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -897,14 +897,16 @@ otherwise. :param int cbufslot: The index in the :data:`ShaderReflection.constantBlocks` list to look up. :param ResourceId buffer: The id of the buffer to use for data. If :data:`ConstantBlock.bufferBacked` is ``False`` this is ignored. -:param int offs: Retrieve buffer contents starting at this byte offset. +:param int offset: Retrieve buffer contents starting at this byte offset. +:param int length: Retrieve this many bytes after :param:`offset`. May be 0 to fetch the rest of the + buffer. :return: The shader variables with their contents. :rtype: ``list`` of :class:`ShaderVariable` )"); virtual rdcarray GetCBufferVariableContents(ResourceId pipeline, ResourceId shader, const char *entryPoint, uint32_t cbufslot, ResourceId buffer, - uint64_t offs) = 0; + uint64_t offset, uint64_t length) = 0; DOCUMENT(R"(Save a texture to a file on disk, with possible transformation to map a complex texture to something compatible with the target file format. diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 7c145c44e..18f2da35c 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -1664,7 +1664,7 @@ void ReplayController::FreeTrace(ShaderDebugTrace *trace) rdcarray ReplayController::GetCBufferVariableContents( ResourceId pipeline, ResourceId shader, const char *entryPoint, uint32_t cbufslot, - ResourceId buffer, uint64_t offs) + ResourceId buffer, uint64_t offset, uint64_t length) { CHECK_REPLAY_THREAD(); @@ -1673,7 +1673,7 @@ rdcarray ReplayController::GetCBufferVariableContents( { buffer = m_pDevice->GetLiveID(buffer); if(buffer != ResourceId()) - m_pDevice->GetBufferData(buffer, offs, 0, data); + m_pDevice->GetBufferData(buffer, offset, length, data); } rdcarray v; diff --git a/renderdoc/replay/replay_controller.h b/renderdoc/replay/replay_controller.h index a953578ad..33e3c9008 100644 --- a/renderdoc/replay/replay_controller.h +++ b/renderdoc/replay/replay_controller.h @@ -202,7 +202,8 @@ public: rdcarray GetCBufferVariableContents(ResourceId pipeline, ResourceId shader, const char *entryPoint, uint32_t cbufslot, - ResourceId buffer, uint64_t offs); + ResourceId buffer, uint64_t offset, + uint64_t length); rdcarray GetSupportedWindowSystems(); diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 32d77fb97..27de58096 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -241,6 +241,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 62ba55050..15e40f72d 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -400,6 +400,9 @@ D3D12\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_truncated_cbuffer.cpp b/util/test/demos/vk/vk_truncated_cbuffer.cpp new file mode 100644 index 000000000..210a4b037 --- /dev/null +++ b/util/test/demos/vk/vk_truncated_cbuffer.cpp @@ -0,0 +1,186 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "vk_test.h" + +RD_TEST(VK_Truncated_CBuffer, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Draws using a cbuffer that is truncated by the descriptor range."; + + std::string common = R"EOSHADER( + +#version 420 core + +struct v2f +{ + vec4 pos; + vec4 col; + vec4 uv; +}; + +)EOSHADER"; + + const std::string vertex = R"EOSHADER( + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; +layout(location = 2) in vec2 UV; + +layout(location = 0) out v2f vertOut; + +void main() +{ + vertOut.pos = vec4(Position.xyz*vec3(1,-1,1), 1); + gl_Position = vertOut.pos; + vertOut.col = Color; + vertOut.uv = vec4(UV.xy, 0, 1); +} + +)EOSHADER"; + + const std::string pixel = R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0) out vec4 Color; + +layout(set = 0, binding = 0, std140) uniform constsbuf +{ + vec4 padding[16]; + vec4 outcol; +}; + +void main() +{ + Color = outcol; +} + +)EOSHADER"; + + void Prepare(int argc, char **argv) + { + // we don't care what behaviour we get but we don't want to completely crash + features.robustBufferAccess = VK_TRUE; + + VulkanGraphicsTest::Prepare(argc, argv); + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + VkDescriptorSetLayout setlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({ + {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT}, + })); + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({setlayout})); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(common + vertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(common + pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + AllocatedBuffer vb( + this, vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(DefaultTri); + + Vec4f data[20]; + + data[16] = Vec4f(1.0f, 2.0f, 3.0f, 4.0f); + + AllocatedBuffer cb( + this, vkh::BufferCreateInfo(sizeof(Vec4f) * 20, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + cb.upload(data); + + VkDescriptorSet descset = allocateDescriptorSet(setlayout); + + vkh::updateDescriptorSets( + device, + { + vkh::WriteDescriptorSet(descset, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + {vkh::DescriptorBufferInfo(cb.buffer, 0, sizeof(Vec4f) * 16)}), + }); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.4f, 0.5f, 0.6f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, {descset}, {}); + vkCmdDraw(cmd, 3, 1, 0, 0); + + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/tests/D3D11/D3D11_CBuffer_Zoo.py b/util/test/tests/D3D11/D3D11_CBuffer_Zoo.py index b4b7fca6d..c26ce512b 100644 --- a/util/test/tests/D3D11/D3D11_CBuffer_Zoo.py +++ b/util/test/tests/D3D11/D3D11_CBuffer_Zoo.py @@ -21,7 +21,7 @@ class D3D11_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself diff --git a/util/test/tests/D3D12/D3D12_CBuffer_Zoo.py b/util/test/tests/D3D12/D3D12_CBuffer_Zoo.py index 41df840d0..a2493fd84 100644 --- a/util/test/tests/D3D12/D3D12_CBuffer_Zoo.py +++ b/util/test/tests/D3D12/D3D12_CBuffer_Zoo.py @@ -43,7 +43,7 @@ class D3D12_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself @@ -396,7 +396,7 @@ class D3D12_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 1, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # float4 zero; var_check.check('root_zero').rows(1).cols(4).value([0.0, 0.0, 0.0, 0.0]) @@ -426,7 +426,7 @@ class D3D12_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 2, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # float4 huge_val; var_check.check('huge_val').rows(1).cols(4).value([64.0, 65.0, 66.0, 67.0]) diff --git a/util/test/tests/GL/GL_CBuffer_Zoo.py b/util/test/tests/GL/GL_CBuffer_Zoo.py index 056f8aa96..18f22dff4 100644 --- a/util/test/tests/GL/GL_CBuffer_Zoo.py +++ b/util/test/tests/GL/GL_CBuffer_Zoo.py @@ -21,7 +21,7 @@ class GL_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself @@ -421,7 +421,7 @@ class GL_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 1, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For bare uniforms we have partial data - only values used in the shader need to get assigned locations and # some drivers are aggressive about stripping any others. Only uniforms with locations get upload values. diff --git a/util/test/tests/Vulkan/VK_Adv_CBuffer_Zoo.py b/util/test/tests/Vulkan/VK_Adv_CBuffer_Zoo.py index 3d7187142..dae111476 100644 --- a/util/test/tests/Vulkan/VK_Adv_CBuffer_Zoo.py +++ b/util/test/tests/Vulkan/VK_Adv_CBuffer_Zoo.py @@ -22,7 +22,7 @@ class VK_Adv_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself diff --git a/util/test/tests/Vulkan/VK_CBuffer_Zoo.py b/util/test/tests/Vulkan/VK_CBuffer_Zoo.py index ff5735b95..b1173f238 100644 --- a/util/test/tests/Vulkan/VK_CBuffer_Zoo.py +++ b/util/test/tests/Vulkan/VK_CBuffer_Zoo.py @@ -28,7 +28,7 @@ class VK_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself @@ -364,7 +364,7 @@ class VK_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 1, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # int A; # Default value 10, untouched @@ -403,7 +403,7 @@ class VK_CBuffer_Zoo(rdtest.TestCase): self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(stage), pipe.GetShaderEntryPoint(stage), 0, - cbuf.resourceId, cbuf.byteOffset)) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize)) # For more detailed reference for the below checks, see the commented definition of the cbuffer # in the shader source code in the demo itself diff --git a/util/test/tests/Vulkan/VK_Spec_Constants.py b/util/test/tests/Vulkan/VK_Spec_Constants.py index f7fd26c77..90dc45c44 100644 --- a/util/test/tests/Vulkan/VK_Spec_Constants.py +++ b/util/test/tests/Vulkan/VK_Spec_Constants.py @@ -36,7 +36,7 @@ class VK_Spec_Constants(rdtest.TestCase): cb_vars = self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(rd.ShaderStage.Pixel), pipe.GetShaderEntryPoint(rd.ShaderStage.Pixel), 0, - cbuf.resourceId, cbuf.byteOffset) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize) self.check(len(cb_vars) == 1) @@ -59,7 +59,7 @@ class VK_Spec_Constants(rdtest.TestCase): cb_vars = self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), pipe.GetShader(rd.ShaderStage.Pixel), pipe.GetShaderEntryPoint(rd.ShaderStage.Pixel), 1, - cbuf.resourceId, cbuf.byteOffset) + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize) self.check(len(cb_vars) == 1) diff --git a/util/test/tests/Vulkan/VK_Truncated_CBuffer.py b/util/test/tests/Vulkan/VK_Truncated_CBuffer.py new file mode 100644 index 000000000..44461b721 --- /dev/null +++ b/util/test/tests/Vulkan/VK_Truncated_CBuffer.py @@ -0,0 +1,32 @@ +import rdtest +import renderdoc as rd + + +class VK_Truncated_CBuffer(rdtest.TestCase): + demos_test_name = 'VK_Truncated_CBuffer' + + def check_capture(self): + draw = self.find_draw("Draw") + + self.check(draw is not None) + + self.controller.SetFrameEvent(draw.eventId, False) + + pipe: rd.PipeState = self.controller.GetPipelineState() + + stage = rd.ShaderStage.Pixel + + cbuf: rd.BoundCBuffer = pipe.GetConstantBuffer(stage, 0, 0) + + variables = self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(), + pipe.GetShader(stage), + pipe.GetShaderEntryPoint(stage), 0, + cbuf.resourceId, cbuf.byteOffset, cbuf.byteSize) + + outcol: rd.ShaderVariable = variables[1] + + self.check(outcol.name == "outcol") + if not rdtest.value_compare(outcol.value.fv[0:4], [0.0, 0.0, 0.0, 0.0]): + raise rdtest.TestFailureException("expected outcol to be 0s, but got {}".format(outcol.value.fv[0:4])) + + rdtest.log.success("CBuffer value was truncated as expected")