import renderdoc as rd
import struct
import rdtest


class VK_Descriptor_Buffer(rdtest.TestCase):
    demos_test_name = 'VK_Descriptor_Buffer'

    def check_capture(self):
        eid = 0

        stage = rd.ShaderStage.Pixel

        for prefix in ["Normal", "Maint6"]:
            for test in range(1000):
                draw = self.find_action(f"{prefix} Test {test}", eid)

                if draw is None:
                    break

                rdtest.log.begin_section(f"{prefix} Test {test}")

                eid = draw.eventId

                self.controller.SetFrameEvent(draw.next.eventId, False)

                pipe = self.controller.GetPipelineState()

                v = pipe.GetViewport(0)

                x = int(v.x) + int(v.width / 2)
                y = int(v.y) + int(v.height // 2)
                offset = int(v.height // 5)

                refl = pipe.GetShaderReflection(stage)

                if refl.debugInfo.debuggable:
                    self.check_debug_pixel(x, y)
                    self.check_debug_pixel(x, y+offset)
                    self.check_debug_pixel(x, y-offset)
                else:
                    rdtest.log.comment(f'Shader is not debuggable, not verifying')

                cbs = pipe.GetConstantBlocks(stage, True)

                out = pipe.GetOutputTargets()[0].resource

                res_data = [
                    (pipe.GetReadOnlyResources(stage, True), refl.readOnlyResources),
                    (pipe.GetReadWriteResources(stage, True), refl.readWriteResources),
                ]

                for reslist,refllist in res_data:
                    for res in reslist:
                        expected_name = refllist[res.access.index].name

                        if refllist[res.access.index].bindArraySize > 1:
                            if (res.access.arrayElement % 10) == 1:
                                if res.descriptor.resource != rd.ResourceId():
                                    raise rdtest.TestFailureException(
                                        f"Expected no resource to be bound at {expected_name}[{res.access.arrayElement}")
                                continue

                            expected_name = f'{expected_name}_{res.access.arrayElement}'

                        if res.descriptor.type == rd.DescriptorType.ImageSampler:
                            sampname = self.get_resource(res.descriptor.secondary).name

                            resname = self.get_resource(res.descriptor.resource).name

                            expected_samp = f'{expected_name}_samp'
                            expected_name = f'{expected_name}_tex'

                            if sampname != expected_samp:
                                raise rdtest.TestFailureException(
                                    f"Expected resource {resname} to be named {expected_name}") 
                        else:
                            resname = self.get_resource(res.descriptor.resource).name

                        if resname != expected_name:
                            raise rdtest.TestFailureException(
                                f"Expected resource {resname} to be named {expected_name}") 

                        # we don't care about sampler/texture contents, it is enough that we get the right name
                        rdtest.log.success(f"Resource {resname} bound as expected")

                for cb in pipe.GetConstantBlocks(stage, True):
                    expected_name = refl.constantBlocks[cb.access.index].name

                    # ignore the push constants
                    if not refl.constantBlocks[cb.access.index].bufferBacked and expected_name == 'push':
                        continue

                    if refl.constantBlocks[cb.access.index].bindArraySize > 1:
                        if (cb.access.arrayElement % 10) == 1:
                            if cb.descriptor.resource != rd.ResourceId():
                                raise rdtest.TestFailureException(
                                    f"Expected no resource to be bound at {expected_name}[{cb.access.arrayElement}")
                            continue

                        expected_name = f'{expected_name}_{cb.access.arrayElement}'

                    resname = self.get_resource(cb.descriptor.resource).name

                    if resname != expected_name:
                        raise rdtest.TestFailureException(
                            f"Expected resource {resname} to be named {expected_name}")

                    data = self.controller.GetBufferData(
                        cb.descriptor.resource, cb.descriptor.byteOffset, cb.descriptor.byteSize)

                    floats = struct.unpack_from("8f", data, 0)

                    picked = self.controller.PickPixel(
                        out, x, y, rd.Subresource(), rd.CompType.Float)

                    output_vec = picked.floatValue[0:4]
                    second_vec = floats[4:8]

                    if not rdtest.value_compare(second_vec, output_vec):
                        raise rdtest.TestFailureException(
                            f"Expected buffer data {output_vec}, but got {second_vec}")

                    data = self.controller.GetCBufferVariableContents(pipe.GetGraphicsPipelineObject(),
                                                                    pipe.GetShader(
                                                                        stage), stage,
                                                                    pipe.GetShaderEntryPoint(
                                                                        stage),
                                                                    cb.access.index, cb.descriptor.resource,
                                                                    cb.descriptor.byteOffset, cb.descriptor.byteSize)

                    if data[0].name != 'data':
                        raise rdtest.TestFailureException(
                            f"Expected 'data' member in cbuffers, got {data[0].name}")

                    if len(data[0].members) < 2:
                        raise rdtest.TestFailureException(
                            f"Expected 'data' member in cbuffers to have at least 2 entries, it has {len(data[0].members)}")

                    second_vec = data[0].members[1].value.f32v[0:4]

                    if not rdtest.value_compare(second_vec, output_vec):
                        raise rdtest.TestFailureException(
                            f"Expected constant data {output_vec}, but got {second_vec}")
                    
                    rdtest.log.success(f"CBuffer {resname} bound as expected with correct data")

                rdtest.log.end_section(f"{prefix} Test {test}")
