AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79582276
Accepted
NeKon
NeKon
Asked: 2025-04-19 18:26:21 +0800 CST2025-04-19 18:26:21 +0800 CST 2025-04-19 18:26:21 +0800 CST

Artefato de renderização progressiva fragmentada

  • 772

Estou desenvolvendo um explorador fractal em C++ usando SFML e std::threadrenderizando o conjunto de Mandelbrot na CPU com exibição progressiva. O objetivo é aproveitar múltiplos núcleos, dividindo a imagem em faixas horizontais, com cada faixa renderizada por uma thread separada. A responsividade do usuário é importante, o que significa que interações como zoom ou panorâmica devem, idealmente, interromper a renderização atual e iniciar uma nova.

Ao implementar a lógica de renderização progressiva, encontrei um artefato visual que é consistentemente reproduzível em um exemplo mínimo.

O Artefato Observado:

Durante o processo de renderização, que visa preencher a imagem faixa por faixa, a saída não mostra faixas sólidas de pixels computados. Em vez disso, dentro da região horizontal atribuída a cada thread, a estrutura fractal aparece como linhas horizontais finas e desconexas, separadas por espaços pretos.

Este não é o preenchimento suave esperado da área de trabalho horizontal. Uma captura de tela ilustrando esse artefato específico e consistente pode ser vista aqui:

https://isstatic.askoverflow.dev/82LBOVUT.png

O que considero particularmente intrigante é a consistência com que esse artefato se manifesta. Não parece ser um "rasgo" transitório ou uma condição de corrida típica; o padrão está presente de forma confiável sempre que executo o código com os mesmos parâmetros.

#include <SFML/Graphics.hpp>
#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
#include <cstring>

struct render_target {
    unsigned int x_start, y_start, x_end, y_end;
    render_target(unsigned int xs, unsigned int ys, unsigned int xe, unsigned int ye)
            : x_start(xs), y_start(ys), x_end(xe), y_end(ye) {}
};

unsigned char* pixels = nullptr;

const unsigned int render_width = 800;
const unsigned int render_height = 600;

const unsigned int buffer_width = render_width;
const unsigned int buffer_height = render_height;

const double zoom_x = 240.0;
const double zoom_y = 240.0;
const double x_offset = 2.25;
const double y_offset = 1.25;
const unsigned int max_iterations = 300;

std::vector<unsigned char> thread_stop_flags; // 0: running, 1: stop requested, 2: stopped

sf::Texture texture;
sf::Sprite sprite(texture);
sf::Image image;

void cpu_render_minimal(render_target target, unsigned char* pixels, unsigned int width_param, unsigned int height_param,
                        double zoom_x, double zoom_y, double x_offset, double y_offset,
                        unsigned int max_iterations,
                        unsigned char& finish_flag)
{
    finish_flag = 0;

    for(unsigned int y = target.y_start; y < target.y_end; ++y){
        for(unsigned int x = target.x_start; x < target.x_end; ++x){
            double zr = 0.0;
            double zi = 0.0;
            double cr = x / zoom_x - x_offset;
            double ci = y / zoom_y - y_offset;

            unsigned int curr_iter = 0;
            while (curr_iter < max_iterations && zr * zr + zi * zi < 4.0) {
                double tmp_zr = zr;
                zr = zr * zr - zi * zi + cr;
                zi = 2.0 * tmp_zr * zi + ci;
                ++curr_iter;

                if(finish_flag == 1) {
                    finish_flag = 2;
                    return;
                }
            }

            unsigned char color_val;
            if (curr_iter == max_iterations) {
                color_val = 255;
            } else {
                color_val = static_cast<unsigned char>((curr_iter % 255) + 1);
            }

            const unsigned int index = (y * width_param + x) * 4;

            if (index + 3 < buffer_width * buffer_height * 4) {
                pixels[index] = color_val;
                pixels[index + 1] = color_val;
                pixels[index + 2] = color_val;
                pixels[index + 3] = 255;
            }
        }
    }
    finish_flag = 1;
}

void post_processing_minimal() {
    if (!pixels) return;

    image = sf::Image({render_width, render_height}, pixels);
    texture = sf::Texture(image, true);
    sprite = sf::Sprite(texture);
}

void start_render_job()
{
    if (pixels != nullptr) {
        delete[] pixels;
        pixels = nullptr;
    }

    pixels = new unsigned char[buffer_width * buffer_height * 4];
    if (!pixels) {
        std::cerr << "Error: Could not allocate pixel buffer!" << std::endl;
        return;
    }
    memset(pixels, 0, buffer_width * buffer_height * 4);

    unsigned int max_threads = std::thread::hardware_concurrency();
    if (max_threads == 0) max_threads = 1;
    thread_stop_flags.assign(max_threads, 0);
    std::vector<render_target> render_targets;

    unsigned int strip_height = render_height / max_threads;
    for(unsigned int i = 0; i < max_threads; ++i) {
        unsigned int x_start = 0;
        unsigned int x_end = render_width;
        unsigned int y_start = strip_height * i;
        unsigned int y_end = (i == max_threads - 1) ? render_height : strip_height * (i + 1);
        if (y_start >= y_end) continue;

        render_targets.emplace_back(x_start, y_start, x_end, y_end);
    }

    for(size_t i = 0; i < render_targets.size(); ++i) {
        std::thread t(cpu_render_minimal, render_targets[i], pixels,
                      render_width, render_height,
                      zoom_x, zoom_y, x_offset, y_offset,
                      max_iterations, std::ref(thread_stop_flags[i]));
        t.detach();
    }

    std::cout << "Started render job with " << render_targets.size() << " threads." << std::endl;
    std::cout << "Buffer dimensions: " << buffer_width << "x" << buffer_height << std::endl;
    std::cout << "Render dimensions passed to threads: " << render_width << "x" << render_height << std::endl;
}

int main() {
    sf::RenderWindow window(sf::VideoMode({render_width, render_height}), "Mandelbrot MRE");
    window.setFramerateLimit(60);

    image = sf::Image({render_width, render_height}, sf::Color::Black);
    texture = sf::Texture(image);
    sprite.setTexture(texture);

    start_render_job();

    while(window.isOpen()){
        while(const auto event = window.pollEvent()) {
            if(event->is<sf::Event::Closed>())
                window.close();
            if (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->scancode == sf::Keyboard::Scancode::Space) {
                std::cout << "Space pressed. Simulating render restart attempt..." << std::endl;
                start_render_job();

                if (pixels) memset(pixels, 0, buffer_width * buffer_height * 4);
            }
        }

        post_processing_minimal();

        window.clear(sf::Color::Black);
        window.draw(sprite);
        window.display();

        static bool render_finished = false;
        if (!render_finished && !thread_stop_flags.empty() &&
            std::all_of(thread_stop_flags.begin(), thread_stop_flags.end(),
                        [](unsigned char state){ return state == 1 || state == 2; }))
        {
            std::cout << "All threads finished (or stopped)." << std::endl;
            render_finished = true;
        }
    }
    if (pixels) {
        delete[] pixels;
        pixels = nullptr;
    }

    return 0;
}


CMakeLists.txt associado:

cmake_minimum_required(VERSION 3.20)
project(MandelbrotMRE LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_TOOLCHAIN_FILE "$ENV{HOME}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")

find_package(SFML 3 COMPONENTS Graphics Window System REQUIRED)

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    add_compile_options(-Wall -Wextra -pedantic -fPIE)
elseif(MSVC)
    add_compile_options(/W4 /MP /std:c++latest)
endif()

set(CPP_SOURCES main.cpp)

add_executable(${PROJECT_NAME} ${CPP_SOURCES})

target_link_libraries(${PROJECT_NAME} PRIVATE
        SFML::Graphics
        SFML::Window
        SFML::System
)

Detalhes do ambiente:

  • SO: Arch Linux
  • Compilador C++: G++
  • Versão SFML: 3
  • Versão do CMake: 3.20+

Perguntas específicas:

  • Por que esse artefato fragmentado específico e consistente aparece durante a renderização progressiva neste código de CPU multithread?

  • Como esse artefato pode ser corrigido de forma confiável?

c++
  • 1 1 respostas
  • 68 Views

1 respostas

  • Voted
  1. Best Answer
    Ahmed AEK
    2025-04-19T19:38:14+08:002025-04-19T19:38:14+08:00

    seu primeiro problema é

    start_render_job();
    if (pixels) memset(pixels, 0, buffer_width * buffer_height * 4);
    

    memsetestá apagando o trabalho feito pelos threads, simplesmente remover memsetaqui corrige seu código.

    seu segundo problema é que suas threads estão trabalhando com globais, o que causa travamentos se o usuário pressiona espaço, e algumas falhas gráficas, a solução aqui é usar std::shared_ptr para enviar recursos compartilhados para uma thread, e nunca acessar o estado global não constante em uma thread.

    void cpu_render_minimal(render_target target, std::shared_ptr<unsigned char[]> pixels, std::shared_ptr<std::vector<unsigned char>> thread_stop_flags, ...
    

    código corrigido completo.

    #include <SFML/Graphics.hpp>
    #include <iostream>
    #include <vector>
    #include <thread>
    #include <chrono>
    #include <memory>
    #include <cstring>
    
    struct render_target {
        unsigned int x_start, y_start, x_end, y_end;
        render_target(unsigned int xs, unsigned int ys, unsigned int xe, unsigned int ye)
            : x_start(xs), y_start(ys), x_end(xe), y_end(ye) {
        }
    };
    
    
    const unsigned int render_width = 1280;
    const unsigned int render_height = 720;
    
    const unsigned int buffer_width = render_width;
    const unsigned int buffer_height = render_height;
    
    const double zoom_x = 500.0;
    const double zoom_y = 500.0;
    const double x_offset = 2.25;
    const double y_offset = 1.25;
    const unsigned int max_iterations = 300;
    
    sf::Texture texture;
    sf::Sprite sprite(texture);
    sf::Image image;
    
    void cpu_render_minimal(render_target target, std::shared_ptr<unsigned char[]> pixels, std::shared_ptr<std::vector<unsigned char>> thread_stop_flags, unsigned int width_param, unsigned int height_param,
        double zoom_x, double zoom_y, double x_offset, double y_offset,
        unsigned int max_iterations,
        unsigned char& finish_flag)
    {
        finish_flag = 0;
    
        for (unsigned int y = target.y_start; y < target.y_end; ++y) {
            for (unsigned int x = target.x_start; x < target.x_end; ++x) {
                double zr = 0.0;
                double zi = 0.0;
                double cr = x / zoom_x - x_offset;
                double ci = y / zoom_y - y_offset;
    
                unsigned int curr_iter = 0;
                while (curr_iter < max_iterations && zr * zr + zi * zi < 4.0) {
                    double tmp_zr = zr;
                    zr = zr * zr - zi * zi + cr;
                    zi = 2.0 * tmp_zr * zi + ci;
                    ++curr_iter;
    
                    if (finish_flag == 1) {
                        finish_flag = 2;
                        return;
                    }
                }
    
                unsigned char color_val;
                if (curr_iter == max_iterations) {
                    color_val = 255;
                }
                else {
                    color_val = static_cast<unsigned char>((curr_iter % 255) + 1);
                }
    
                const unsigned int index = (y * width_param + x) * 4;
    
                if (index + 3 < buffer_width * buffer_height * 4) {
                    pixels[index] = color_val;
                    pixels[index + 1] = color_val;
                    pixels[index + 2] = color_val;
                    pixels[index + 3] = 255;
                }
            }
        }
        finish_flag = 1;
    }
    
    void post_processing_minimal(std::shared_ptr<unsigned char[]>& pixels) {
        if (!pixels) return;
    
        image = sf::Image({ render_width, render_height }, pixels.get());
        texture = sf::Texture(image, true);
        sprite = sf::Sprite(texture);
    }
    
    void start_render_job(std::shared_ptr<unsigned char[]>& pixels, std::shared_ptr<std::vector<unsigned char>>& thread_stop_flags)
    {
        const size_t buff_size = buffer_width * buffer_height * 4;
        pixels = std::shared_ptr<unsigned char[]>(new unsigned char[buff_size]);
        memset(pixels.get(), 0, buff_size);
    
        unsigned int max_threads = std::thread::hardware_concurrency();
        if (max_threads == 0) max_threads = 1;
        if (thread_stop_flags)
        {
            for (auto& entry : *thread_stop_flags)
            {
                entry = 1;
            }
        }
        thread_stop_flags = std::make_shared<std::vector<unsigned char>>();
        thread_stop_flags->resize(max_threads);
        std::vector<render_target> render_targets;
    
        unsigned int strip_height = render_height / max_threads;
        for (unsigned int i = 0; i < max_threads; ++i) {
            unsigned int x_start = 0;
            unsigned int x_end = render_width;
            unsigned int y_start = strip_height * i;
            unsigned int y_end = (i == max_threads - 1) ? render_height : strip_height * (i + 1);
            if (y_start >= y_end) continue;
    
            render_targets.emplace_back(x_start, y_start, x_end, y_end);
        }
    
        for (size_t i = 0; i < render_targets.size(); ++i) {
            std::thread t(cpu_render_minimal, render_targets[i], pixels, thread_stop_flags,
                render_width, render_height,
                zoom_x, zoom_y, x_offset, y_offset,
                max_iterations, std::ref((*thread_stop_flags)[i]));
            t.detach();
        }
    
        std::cout << "Started render job with " << render_targets.size() << " threads." << std::endl;
        std::cout << "Buffer dimensions: " << buffer_width << "x" << buffer_height << std::endl;
        std::cout << "Render dimensions passed to threads: " << render_width << "x" << render_height << std::endl;
    }
    
    int main() {
        sf::RenderWindow window(sf::VideoMode({ render_width, render_height }), "Mandelbrot MRE");
        window.setFramerateLimit(60);
    
        image = sf::Image({ render_width, render_height }, sf::Color::Black);
        texture = sf::Texture(image);
        sprite.setTexture(texture);
        std::shared_ptr<unsigned char[]> pixels;
        std::shared_ptr<std::vector<unsigned char>> thread_stop_flags;
        start_render_job(pixels, thread_stop_flags);
    
        while (window.isOpen()) {
            while (const auto event = window.pollEvent()) {
                if (event->is<sf::Event::Closed>())
                    window.close();
                if (event->is<sf::Event::KeyPressed>() && event->getIf<sf::Event::KeyPressed>()->scancode == sf::Keyboard::Scancode::Space) {
                    std::cout << "Space pressed. Simulating render restart attempt..." << std::endl;
                    start_render_job(pixels, thread_stop_flags);
    
                }
            }
    
            post_processing_minimal(pixels);
    
            window.clear(sf::Color::Black);
            window.draw(sprite);
            window.display();
    
            static bool render_finished = false;
            if (!render_finished && !thread_stop_flags->empty() &&
                std::all_of(thread_stop_flags->begin(), thread_stop_flags->end(),
                    [](unsigned char state) { return state == 1 || state == 2; }))
            {
                std::cout << "All threads finished (or stopped)." << std::endl;
                render_finished = true;
            }
        }
    
        return 0;
    }
    

    Por fim, acessar finish_flago loop sem sincronização é um comportamento indefinido; você deve usar std::atomic<unsigned char>, mas isso provavelmente não causará uma falha de qualquer maneira.

    • 8

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve