void RenderRasterLayer::render(PaintParameters& parameters) {
    if (parameters.pass != RenderPass::Translucent)
        return;
    const auto& evaluated = static_cast<const RasterLayerProperties&>(*evaluatedProperties).evaluated;
    RasterProgram::Binders paintAttributeData{ evaluated, 0 };

    auto draw = [&] (const mat4& matrix,
                     const auto& vertexBuffer,
                     const auto& indexBuffer,
                     const auto& segments,
                     const auto& textureBindings,
                     const std::string& drawScopeID) {
        auto& programInstance = parameters.programs.getRasterLayerPrograms().raster;

        const auto allUniformValues = programInstance.computeAllUniformValues(
            RasterProgram::LayoutUniformValues {
                uniforms::matrix::Value( matrix ),
                uniforms::opacity::Value( evaluated.get<RasterOpacity>() ),
                uniforms::fade_t::Value( 1 ),
                uniforms::brightness_low::Value( evaluated.get<RasterBrightnessMin>() ),
                uniforms::brightness_high::Value( evaluated.get<RasterBrightnessMax>() ),
                uniforms::saturation_factor::Value( saturationFactor(evaluated.get<RasterSaturation>()) ),
                uniforms::contrast_factor::Value( contrastFactor(evaluated.get<RasterContrast>()) ),
                uniforms::spin_weights::Value( spinWeights(evaluated.get<RasterHueRotate>()) ),
                uniforms::buffer_scale::Value( 1.0f ),
                uniforms::scale_parent::Value( 1.0f ),
                uniforms::tl_parent::Value( std::array<float, 2> {{ 0.0f, 0.0f }} ),
            },
            paintAttributeData,
            evaluated,
            parameters.state.getZoom()
        );
        const auto allAttributeBindings = programInstance.computeAllAttributeBindings(
            vertexBuffer,
            paintAttributeData,
            evaluated
        );

        checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings));

        programInstance.draw(
            parameters.context,
            *parameters.renderPass,
            gfx::Triangles(),
            parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly),
            gfx::StencilMode::disabled(),
            parameters.colorModeForRenderPass(),
            gfx::CullFaceMode::disabled(),
            indexBuffer,
            segments,
            allUniformValues,
            allAttributeBindings,
            textureBindings,
            getID() + "/" + drawScopeID
        );
    };

    const gfx::TextureFilterType filter = evaluated.get<RasterResampling>() == RasterResamplingType::Nearest ? gfx::TextureFilterType::Nearest : gfx::TextureFilterType::Linear;

    if (imageData && !imageData->bucket->needsUpload()) {
        RasterBucket& bucket = *imageData->bucket;
        assert(bucket.texture);

        size_t i = 0;
        for (const auto& matrix_ : *imageData->matrices) {
            draw(matrix_,
                *bucket.vertexBuffer,
                *bucket.indexBuffer,
                bucket.segments,
                RasterProgram::TextureBindings{
                    textures::image0::Value{ bucket.texture->getResource(), filter },
                    textures::image1::Value{ bucket.texture->getResource(), filter },
                },
                bucket.drawScopeID + std::to_string(i++));
        }
    } else {
        for (const RenderTile& tile : renderTiles) {
            auto* bucket_ = tile.getBucket(*baseImpl);
            if (!bucket_) {
                continue;
            }
            auto& bucket = static_cast<RasterBucket&>(*bucket_);

            if (!bucket.hasData())
                continue;

            assert(bucket.texture);
            if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) {
                // Draw only the parts of the tile that aren't drawn by another tile in the layer.
                draw(parameters.matrixForTile(tile.id, true),
                     *bucket.vertexBuffer,
                     *bucket.indexBuffer,
                     bucket.segments,
                     RasterProgram::TextureBindings{
                         textures::image0::Value{ bucket.texture->getResource(), filter },
                         textures::image1::Value{ bucket.texture->getResource(), filter },
                     },
                     bucket.drawScopeID);
            } else {
                // Draw the full tile.
                draw(parameters.matrixForTile(tile.id, true),
                     *parameters.staticData.rasterVertexBuffer,
                     *parameters.staticData.quadTriangleIndexBuffer,
                     parameters.staticData.rasterSegments,
                     RasterProgram::TextureBindings{
                         textures::image0::Value{ bucket.texture->getResource(), filter },
                         textures::image1::Value{ bucket.texture->getResource(), filter },
                     },
                     bucket.drawScopeID);
            }
        }
    }
}
void RenderHillshadeLayer::render(PaintParameters& parameters) {
    if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D)
        return;
    const auto& evaluated = static_cast<const HillshadeLayerProperties&>(*evaluatedProperties).evaluated;  
    auto draw = [&] (const mat4& matrix,
                     const auto& vertexBuffer,
                     const auto& indexBuffer,
                     const auto& segments,
                     const UnwrappedTileID& id,
                     const auto& textureBindings) {
        auto& programInstance = parameters.programs.getHillshadeLayerPrograms().hillshade;

        const HillshadeProgram::Binders paintAttributeData{ evaluated, 0 };

        const auto allUniformValues = programInstance.computeAllUniformValues(
            HillshadeProgram::LayoutUniformValues {
                uniforms::matrix::Value( matrix ),
                uniforms::highlight::Value( evaluated.get<HillshadeHighlightColor>() ),
                uniforms::shadow::Value( evaluated.get<HillshadeShadowColor>() ),
                uniforms::accent::Value( evaluated.get<HillshadeAccentColor>() ),
                uniforms::light::Value( getLight(parameters) ),
                uniforms::latrange::Value( getLatRange(id) ),
            },
            paintAttributeData,
            evaluated,
            parameters.state.getZoom()
        );
        const auto allAttributeBindings = programInstance.computeAllAttributeBindings(
            vertexBuffer,
            paintAttributeData,
            evaluated
        );

        checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings));

        programInstance.draw(
            parameters.context,
            *parameters.renderPass,
            gfx::Triangles(),
            parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly),
            gfx::StencilMode::disabled(),
            parameters.colorModeForRenderPass(),
            gfx::CullFaceMode::disabled(),
            indexBuffer,
            segments,
            allUniformValues,
            allAttributeBindings,
            textureBindings,
            getID()
        );
    };

    mat4 mat;
    matrix::ortho(mat, 0, util::EXTENT, -util::EXTENT, 0, 0, 1);
    matrix::translate(mat, mat, 0, -util::EXTENT, 0);

    for (const RenderTile& tile : renderTiles) {
        auto* bucket_ = tile.getBucket(*baseImpl);
        if (!bucket_) {
            continue;
        }
        auto& bucket = static_cast<HillshadeBucket&>(*bucket_);

        if (!bucket.hasData()){
            continue;
        }

        if (!bucket.isPrepared() && parameters.pass == RenderPass::Pass3D) {
            assert(bucket.dem);
            const uint16_t stride = bucket.getDEMData().stride;
            const uint16_t tilesize = bucket.getDEMData().dim;
            auto view = parameters.context.createOffscreenTexture({ tilesize, tilesize });

            auto renderPass = parameters.encoder->createRenderPass(
                "hillshade prepare", { *view, Color{ 0.0f, 0.0f, 0.0f, 0.0f }, {}, {} });

            const Properties<>::PossiblyEvaluated properties;
            const HillshadePrepareProgram::Binders paintAttributeData{ properties, 0 };
            
            auto& programInstance = parameters.programs.getHillshadeLayerPrograms().hillshadePrepare;

            const auto allUniformValues = programInstance.computeAllUniformValues(
                HillshadePrepareProgram::LayoutUniformValues {
                    uniforms::matrix::Value( mat ),
                    uniforms::dimension::Value( {{stride, stride}} ),
                    uniforms::zoom::Value( float(tile.id.canonical.z) ),
                    uniforms::maxzoom::Value( float(maxzoom) ),
                },
                paintAttributeData,
                properties,
                parameters.state.getZoom()
            );
            const auto allAttributeBindings = programInstance.computeAllAttributeBindings(
                *parameters.staticData.rasterVertexBuffer,
                paintAttributeData,
                properties
            );

            checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings));

            programInstance.draw(
                parameters.context,
                *renderPass,
                gfx::Triangles(),
                parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly),
                gfx::StencilMode::disabled(),
                parameters.colorModeForRenderPass(),
                gfx::CullFaceMode::disabled(),
                *parameters.staticData.quadTriangleIndexBuffer,
                parameters.staticData.rasterSegments,
                allUniformValues,
                allAttributeBindings,
                HillshadePrepareProgram::TextureBindings{
                    textures::image::Value{ bucket.dem->getResource() },
                },
                getID()
            );
            bucket.texture = std::move(view->getTexture());
            bucket.setPrepared(true);
        } else if (parameters.pass == RenderPass::Translucent) {
            assert(bucket.texture);

            if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) {
                // Draw only the parts of the tile that aren't drawn by another tile in the layer.
                draw(parameters.matrixForTile(tile.id, true),
                     *bucket.vertexBuffer,
                     *bucket.indexBuffer,
                     bucket.segments,
                     tile.id,
                     HillshadeProgram::TextureBindings{
                         textures::image::Value{ bucket.texture->getResource(), gfx::TextureFilterType::Linear },
                     });
            } else {
                // Draw the full tile.
                draw(parameters.matrixForTile(tile.id, true),
                     *parameters.staticData.rasterVertexBuffer,
                     *parameters.staticData.quadTriangleIndexBuffer,
                     parameters.staticData.rasterSegments,
                     tile.id,
                     HillshadeProgram::TextureBindings{
                         textures::image::Value{ bucket.texture->getResource(), gfx::TextureFilterType::Linear },
                     });
            }
        }
        

    }
}