WebGPU দিয়ে একটি অ্যাপ তৈরি করুন

ফ্রাঁসোয়া বিউফোর্ট
François Beaufort

ওয়েব ডেভেলপারদের জন্য, WebGPU হল একটি ওয়েব গ্রাফিক্স API যা GPU-তে একীভূত এবং দ্রুত অ্যাক্সেস প্রদান করে। WebGPU আধুনিক হার্ডওয়্যার সক্ষমতা প্রকাশ করে এবং Direct3D 12, Metal, এবং Vulkan-এর মতো GPU-তে রেন্ডারিং এবং গণনা করার অনুমতি দেয়।

যদিও সত্য, সেই গল্পটি অসম্পূর্ণ। WebGPU হল অ্যাপল, গুগল, ইন্টেল, মজিলা এবং মাইক্রোসফ্টের মতো বড় কোম্পানিগুলি সহ একটি সহযোগী প্রচেষ্টার ফলাফল। তাদের মধ্যে, কেউ কেউ বুঝতে পেরেছিলেন যে WebGPU একটি Javascript API এর চেয়ে বেশি হতে পারে, কিন্তু ওয়েব ছাড়া অন্য ইকোসিস্টেম জুড়ে বিকাশকারীদের জন্য একটি ক্রস-প্ল্যাটফর্ম গ্রাফিক্স API।

প্রাথমিক ব্যবহারের ক্ষেত্রে, Chrome 113-এ একটি JavaScript API প্রবর্তন করা হয়েছিল। যাইহোক, এটির পাশাপাশি আরেকটি উল্লেখযোগ্য প্রকল্প তৈরি করা হয়েছে: webgpu.h C API। এই C শিরোনাম ফাইলটি WebGPU এর সমস্ত উপলব্ধ পদ্ধতি এবং ডেটা স্ট্রাকচার তালিকাভুক্ত করে। এটি একটি প্ল্যাটফর্ম-অজ্ঞেয়বাদী হার্ডওয়্যার বিমূর্ত স্তর হিসাবে কাজ করে, যা আপনাকে বিভিন্ন প্ল্যাটফর্ম জুড়ে একটি সামঞ্জস্যপূর্ণ ইন্টারফেস প্রদান করে প্ল্যাটফর্ম-নির্দিষ্ট অ্যাপ্লিকেশন তৈরি করতে দেয়।

এই নথিতে, আপনি ওয়েবজিপিউ ব্যবহার করে একটি ছোট C++ অ্যাপ কীভাবে লিখবেন তা শিখবেন যা ওয়েব এবং নির্দিষ্ট প্ল্যাটফর্ম উভয়েই চলে। স্পয়লার সতর্কতা, আপনি একই লাল ত্রিভুজ পাবেন যা ব্রাউজার উইন্ডোতে প্রদর্শিত হয় এবং একটি ডেস্কটপ উইন্ডোতে আপনার কোডবেসে ন্যূনতম সমন্বয় সহ।

একটি ব্রাউজার উইন্ডোতে WebGPU দ্বারা চালিত একটি লাল ত্রিভুজের স্ক্রিনশট এবং macOS-এ একটি ডেস্কটপ উইন্ডো৷
একটি ব্রাউজার উইন্ডো এবং একটি ডেস্কটপ উইন্ডোতে WebGPU দ্বারা চালিত একই ত্রিভুজ।

এটা কিভাবে কাজ করে?

সম্পূর্ণ অ্যাপ্লিকেশন দেখতে WebGPU ক্রস-প্ল্যাটফর্ম অ্যাপ রিপোজিটরি দেখুন।

অ্যাপটি হল একটি সংক্ষিপ্ত C++ উদাহরণ যা দেখায় কিভাবে একটি একক কোডবেস থেকে ডেস্কটপ এবং ওয়েব অ্যাপ তৈরি করতে WebGPU ব্যবহার করতে হয়। হুডের নিচে, এটি WebGPU-এর webgpu.h-কে একটি প্ল্যাটফর্ম-অজ্ঞেয়বাদী হার্ডওয়্যার বিমূর্তকরণ স্তর হিসাবে একটি C++ র‍্যাপার ওয়েবgpu_cpp.h নামে ব্যবহার করে।

ওয়েবে, অ্যাপটি Emscripten এর বিপরীতে তৈরি করা হয়েছে, যা JavaScript API-এর উপরে webgpu.h প্রয়োগকারী বাইন্ডিং রয়েছে। macOS বা Windows এর মতো নির্দিষ্ট প্ল্যাটফর্মে, এই প্রকল্পটি Dawn , Chromium-এর ক্রস-প্ল্যাটফর্ম WebGPU বাস্তবায়নের বিপরীতে তৈরি করা যেতে পারে। এটা উল্লেখ করার মতো wgpu-native , webgpu.h এর একটি মরিচা বাস্তবায়ন, এছাড়াও বিদ্যমান কিন্তু এই নথিতে ব্যবহার করা হয় না।

শুরু করুন

শুরু করতে, আপনাকে একটি C++ কম্পাইলার এবং CMake ক্রস-প্ল্যাটফর্ম বিল্ডগুলিকে একটি আদর্শ উপায়ে পরিচালনা করতে হবে। একটি ডেডিকেটেড ফোল্ডারের ভিতরে, একটি main.cpp সোর্স ফাইল এবং একটি CMakeLists.txt বিল্ড ফাইল তৈরি করুন৷

main.cpp ফাইলে আপাতত একটি খালি main() ফাংশন থাকা উচিত।

int main() {}

CMakeLists.txt ফাইলটিতে প্রকল্প সম্পর্কে প্রাথমিক তথ্য রয়েছে। শেষ লাইনটি উল্লেখ করে যে এক্সিকিউটেবল নাম "অ্যাপ" এবং এর সোর্স কোড হল main.cpp

cmake_minimum_required(VERSION 3.13) # CMake version check
project(app)                         # Create project "app"
set(CMAKE_CXX_STANDARD 20)           # Enable C++20 standard

add_executable(app "main.cpp")

একটি "build/" সাব ফোল্ডারে বিল্ড ফাইল তৈরি করতে cmake -B build চালান এবং অ্যাপটি তৈরি করতে এবং এক্সিকিউটেবল ফাইল জেনারেট করতে cmake --build build চালান।

# Build the app with CMake.
$ cmake -B build && cmake --build build

# Run the app.
$ ./build/app

অ্যাপটি চলে কিন্তু এখনও কোনও আউটপুট নেই, কারণ আপনার স্ক্রিনে জিনিস আঁকার একটি উপায় প্রয়োজন।

ডন পান

আপনার ত্রিভুজ আঁকতে, আপনি Dawn , Chromium-এর ক্রস-প্ল্যাটফর্ম WebGPU বাস্তবায়নের সুবিধা নিতে পারেন। এতে পর্দায় আঁকার জন্য GLFW C++ লাইব্রেরি রয়েছে। ডন ডাউনলোড করার একটি উপায় হল এটিকে আপনার সংগ্রহস্থলে একটি গিট সাবমডিউল হিসাবে যুক্ত করা। নিম্নলিখিত কমান্ডগুলি এটিকে "dawn/" সাব ফোল্ডারে নিয়ে আসে।

$ git init
$ git submodule add https://dawn.googlesource.com/dawn

তারপরে, CMakeLists.txt ফাইলে নিম্নরূপ যুক্ত করুন:

  • CMake DAWN_FETCH_DEPENDENCIES বিকল্পটি সমস্ত ডন নির্ভরতা নিয়ে আসে।
  • dawn/ সাব ফোল্ডার টার্গেটে অন্তর্ভুক্ত করা হয়েছে।
  • আপনার অ্যাপ নির্ভর করবে dawn::webgpu_dawn , glfw , এবং webgpu_glfw টার্গেটের উপর যাতে আপনি সেগুলিকে পরে main.cpp ফাইলে ব্যবহার করতে পারেন।

set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)

একটি জানালা খুলুন

এখন যেহেতু ডন উপলব্ধ, স্ক্রিনে জিনিসগুলি আঁকতে GLFW ব্যবহার করুন৷ সুবিধার জন্য webgpu_glfw তে অন্তর্ভুক্ত এই লাইব্রেরিটি আপনাকে উইন্ডো পরিচালনার জন্য প্ল্যাটফর্ম-অজ্ঞেয়বাদী কোড লিখতে দেয়।

512x512 রেজোলিউশন সহ "WebGPU উইন্ডো" নামে একটি উইন্ডো খুলতে, নীচের মত main.cpp ফাইলটি আপডেট করুন৷ উল্লেখ্য যে glfwWindowHint() এখানে ব্যবহার করা হয়েছে কোনো বিশেষ গ্রাফিক্স API আরম্ভ করার অনুরোধ করতে।

#include <GLFW/glfw3.h>

const uint32_t kWidth = 512;
const uint32_t kHeight = 512;

void Start() {
  if (!glfwInit()) {
    return;
  }

  glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
  GLFWwindow* window =
      glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    // TODO: Render a triangle using WebGPU.
  }
}

int main() {
  Start();
}

অ্যাপটি পুনর্নির্মাণ করা এবং এটিকে আগের মতো চালানোর ফলে একটি খালি উইন্ডো দেখা যায়। আপনি উন্নতি করছেন!

একটি খালি macOS উইন্ডোর স্ক্রিনশট।
একটা খালি জানালা।

GPU ডিভাইস পান

জাভাস্ক্রিপ্টে, navigator.gpu হল GPU অ্যাক্সেস করার জন্য আপনার এন্ট্রিপয়েন্ট। C++ এ, আপনাকে ম্যানুয়ালি একটি wgpu::Instance ভেরিয়েবল তৈরি করতে হবে যা একই উদ্দেশ্যে ব্যবহৃত হয়। সুবিধার জন্য, main.cpp ফাইলের উপরে instance ঘোষণা করুন এবং main() ভিতরে wgpu::CreateInstance() কল করুন।


#include <webgpu/webgpu_cpp.h>

wgpu::Instance instance;


int main() {
  instance = wgpu::CreateInstance();
  Start();
}

জাভাস্ক্রিপ্ট API এর আকৃতির কারণে GPU অ্যাক্সেস করা অ্যাসিঙ্ক্রোনাস। C++ এ, GetAdapter() এবং GetDevice() নামে দুটি সহায়ক ফাংশন তৈরি করুন যা যথাক্রমে একটি wgpu::Adapter এবং একটি wgpu::Device সহ একটি কলব্যাক ফাংশন প্রদান করে।

#include <iostream>


void GetAdapter(void (*callback)(wgpu::Adapter)) {
  instance.RequestAdapter(
      nullptr,
      [](WGPURequestAdapterStatus status, WGPUAdapter cAdapter,
         const char* message, void* userdata) {
        if (status != WGPURequestAdapterStatus_Success) {
          exit(0);
        }
        wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
        reinterpret_cast<void (*)(wgpu::Adapter)>(userdata)(adapter);
  }, reinterpret_cast<void*>(callback));
}

void GetDevice(void (*callback)(wgpu::Device)) {
  adapter.RequestDevice(
      nullptr,
      [](WGPURequestDeviceStatus status, WGPUDevice cDevice,
          const char* message, void* userdata) {
        wgpu::Device device = wgpu::Device::Acquire(cDevice);
        device.SetUncapturedErrorCallback(
            [](WGPUErrorType type, const char* message, void* userdata) {
              std::cout << "Error: " << type << " - message: " << message;
            },
            nullptr);
        reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device);
  }, reinterpret_cast<void*>(callback));
}

সহজে অ্যাক্সেসের জন্য, main.cpp ফাইলের শীর্ষে দুটি ভেরিয়েবল wgpu::Adapter এবং wgpu::Device ঘোষণা করুন। GetAdapter() কল করতে main() ফাংশন আপডেট করুন এবং এর ফলাফল কলব্যাক adapter বরাদ্দ করুন তারপর GetDevice() কল করুন এবং Start() কল করার আগে device এর ফলাফল কলব্যাক বরাদ্দ করুন।

wgpu::Adapter adapter;
wgpu::Device device;


int main() {
  instance = wgpu::CreateInstance();
  GetAdapter([](wgpu::Adapter a) {
    adapter = a;
    GetDevice([](wgpu::Device d) {
      device = d;
      Start();
    });
  });
}

একটি ত্রিভুজ আঁকুন

জাভাস্ক্রিপ্ট এপিআইতে সোয়াপ চেইনটি প্রকাশ করা হয় না কারণ ব্রাউজার এটির যত্ন নেয়। C++ এ, আপনাকে এটি ম্যানুয়ালি তৈরি করতে হবে। আবার, সুবিধার জন্য, main.cpp ফাইলের শীর্ষে একটি wgpu::Surface ভেরিয়েবল ঘোষণা করুন। Start() এ GLFW উইন্ডো তৈরি করার পর, একটি wgpu:: wgpu::Surface (একটি HTML ক্যানভাসের অনুরূপ) তৈরি করতে সহজ wgpu::glfw::CreateSurfaceForWindow() ফাংশনটি কল করুন এবং নতুন সহায়ক ConfigureSurface() ফাংশনকে কল করে এটি কনফিগার করুন। InitGraphics() এ। ওয়েইল লুপে পরবর্তী টেক্সচার উপস্থাপন করতে আপনাকে surface.Present() কল করতে হবে। এটির কোনো দৃশ্যমান প্রভাব নেই কারণ এখনো কোনো রেন্ডারিং হচ্ছে না।

#include <webgpu/webgpu_glfw.h>


wgpu::Surface surface;
wgpu::TextureFormat format;

void ConfigureSurface() {
  wgpu::SurfaceCapabilities capabilities;
  surface.GetCapabilities(adapter, &capabilities);
  format = capabilities.formats[0];

  wgpu::SurfaceConfiguration config{
      .device = device,
      .format = format,
      .width = kWidth,
      .height = kHeight};
  surface.Configure(&config);
}

void InitGraphics() {
  ConfigureSurface();
}

void Render() {
  // TODO: Render a triangle using WebGPU.
}

void Start() {
  
  surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);

  InitGraphics();

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    surface.Present();
    instance.ProcessEvents();
  }
}

নীচের কোডটি দিয়ে রেন্ডার পাইপলাইন তৈরি করার এখন একটি ভাল সময়। সহজে অ্যাক্সেসের জন্য, main.cpp ফাইলের শীর্ষে একটি wgpu::RenderPipeline ভেরিয়েবল ঘোষণা করুন এবং InitGraphics() -এ সাহায্যকারী ফাংশন CreateRenderPipeline() কল করুন।

wgpu::RenderPipeline pipeline;


const char shaderCode[] = R"(
    @vertex fn vertexMain(@builtin(vertex_index) i : u32) ->
      @builtin(position) vec4f {
        const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1));
        return vec4f(pos[i], 0, 1);
    }
    @fragment fn fragmentMain() -> @location(0) vec4f {
        return vec4f(1, 0, 0, 1);
    }
)";

void CreateRenderPipeline() {
  wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
  wgslDesc.code = shaderCode;

  wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
      .nextInChain = &wgslDesc};
  wgpu::ShaderModule shaderModule =
      device.CreateShaderModule(&shaderModuleDescriptor);

  wgpu::ColorTargetState colorTargetState{.format = format};

  wgpu::FragmentState fragmentState{.module = shaderModule,
                                    .targetCount = 1,
                                    .targets = &colorTargetState};

  wgpu::RenderPipelineDescriptor descriptor{
      .vertex = {.module = shaderModule},
      .fragment = &fragmentState};
  pipeline = device.CreateRenderPipeline(&descriptor);
}

void InitGraphics() {
  
  CreateRenderPipeline();
}

অবশেষে, প্রতিটি ফ্রেম নামক Render() ফাংশনে GPU-তে রেন্ডারিং কমান্ড পাঠান।

void Render() {
  wgpu::SurfaceTexture surfaceTexture;
  surface.GetCurrentTexture(&surfaceTexture);

  wgpu::RenderPassColorAttachment attachment{
      .view = surfaceTexture.texture.CreateView(),
      .loadOp = wgpu::LoadOp::Clear,
      .storeOp = wgpu::StoreOp::Store};

  wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
                                        .colorAttachments = &attachment};

  wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
  wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
  pass.SetPipeline(pipeline);
  pass.Draw(3);
  pass.End();
  wgpu::CommandBuffer commands = encoder.Finish();
  device.GetQueue().Submit(1, &commands);
}

CMake এর সাথে অ্যাপটিকে পুনর্নির্মাণ করা এবং এটিকে এখন চালানোর ফলে একটি উইন্ডোতে দীর্ঘ প্রতীক্ষিত লাল ত্রিভুজ দেখা যায়! একটি বিরতি নিন - আপনি এটি প্রাপ্য.

একটি macOS উইন্ডোতে একটি লাল ত্রিভুজের স্ক্রিনশট৷
ডেস্কটপের উইন্ডোতে একটি লাল ত্রিভুজ।

WebAssembly কম্পাইল

ব্রাউজার উইন্ডোতে এই লাল ত্রিভুজটি আঁকতে আপনার বিদ্যমান কোডবেস সামঞ্জস্য করার জন্য প্রয়োজনীয় ন্যূনতম পরিবর্তনগুলি এখন দেখে নেওয়া যাক। আবার, অ্যাপটি Emscripten- এর বিপরীতে তৈরি করা হয়েছে, C/C++ প্রোগ্রামগুলিকে WebAssembly-এ কম্পাইল করার একটি টুল, যেটিতে JavaScript API-এর উপরে webgpu.h বাস্তবায়নের বাইন্ডিং রয়েছে।

CMake সেটিংস আপডেট করুন

Emscripten ইনস্টল হয়ে গেলে, CMakeLists.txt বিল্ড ফাইলটি নিম্নরূপ আপডেট করুন। হাইলাইট করা কোডটিই আপনাকে পরিবর্তন করতে হবে।

  • set_target_properties স্বয়ংক্রিয়ভাবে লক্ষ্য ফাইলে "html" ফাইল এক্সটেনশন যোগ করতে ব্যবহৃত হয়। অন্য কথায়, আপনি একটি "app.html" ফাইল তৈরি করবেন।
  • Emscripten-এ WebGPU সমর্থন সক্ষম করতে USE_WEBGPU অ্যাপ লিঙ্ক বিকল্পের প্রয়োজন। এটি ছাড়া, আপনার main.cpp ফাইল webgpu/webgpu_cpp.h ফাইল অ্যাক্সেস করতে পারবে না।
  • USE_GLFW অ্যাপ লিঙ্ক বিকল্পটি এখানেও প্রয়োজন যাতে আপনি আপনার GLFW কোড পুনরায় ব্যবহার করতে পারেন।
cmake_minimum_required(VERSION 3.13) # CMake version check
project(app)                         # Create project "app"
set(CMAKE_CXX_STANDARD 20)           # Enable C++20 standard

add_executable(app "main.cpp")

if(EMSCRIPTEN)
  set_target_properties(app PROPERTIES SUFFIX ".html")
  target_link_options(app PRIVATE "-sUSE_WEBGPU=1" "-sUSE_GLFW=3")
else()
  set(DAWN_FETCH_DEPENDENCIES ON)
  add_subdirectory("dawn" EXCLUDE_FROM_ALL)
  target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
endif()

কোড আপডেট করুন

এমস্ক্রিপ্টেনে, একটি wgpu::surface তৈরি করার জন্য একটি HTML ক্যানভাস উপাদান প্রয়োজন। এর জন্য, instance.CreateSurface() কল করুন এবং Emscripten দ্বারা উত্পন্ন HTML পৃষ্ঠায় উপযুক্ত HTML ক্যানভাস উপাদানের সাথে মেলে #canvas নির্বাচককে নির্দিষ্ট করুন।

কিছুক্ষণ লুপ ব্যবহার করার পরিবর্তে, ব্রাউজার এবং মনিটরের সাথে সঠিকভাবে Render() ফাংশনটি সঠিক মসৃণ হারে কল করা হয়েছে তা নিশ্চিত করতে emscripten_set_main_loop(Render) কল করুন।

#include <GLFW/glfw3.h>
#include <webgpu/webgpu_cpp.h>
#include <iostream>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#else
#include <webgpu/webgpu_glfw.h>
#endif
void Start() {
  if (!glfwInit()) {
    return;
  }

  glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
  GLFWwindow* window =
      glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);

#if defined(__EMSCRIPTEN__)
  wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
  canvasDesc.selector = "#canvas";

  wgpu::SurfaceDescriptor surfaceDesc{.nextInChain = &canvasDesc};
  surface = instance.CreateSurface(&surfaceDesc);
#else
  surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
#endif

  InitGraphics();

#if defined(__EMSCRIPTEN__)
  emscripten_set_main_loop(Render, 0, false);
#else
  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    Render();
    surface.Present();
    instance.ProcessEvents();
  }
#endif
}

Emscripten দিয়ে অ্যাপটি তৈরি করুন

Emscripten-এর সাহায্যে অ্যাপটি তৈরি করার জন্য প্রয়োজন একমাত্র পরিবর্তন হল যাদুকর emcmake শেল স্ক্রিপ্টের সাথে cmake কমান্ডগুলিকে প্রিপেন্ড করা। এইবার, একটি build-web সাব ফোল্ডারে অ্যাপটি তৈরি করুন এবং একটি HTTP সার্ভার শুরু করুন। অবশেষে, আপনার ব্রাউজার খুলুন এবং build-web/app.html যান।

# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web

# Start a HTTP server.
$ npx http-server
ব্রাউজার উইন্ডোতে একটি লাল ত্রিভুজের স্ক্রিনশট।
ব্রাউজার উইন্ডোতে একটি লাল ত্রিভুজ।

এরপর কি

আপনি ভবিষ্যতে কি আশা করতে পারেন তা এখানে:

  • webgpu.h এবং webgpu_cpp.h API-এর স্থিতিশীলতার উন্নতি।
  • অ্যান্ড্রয়েড এবং আইওএসের জন্য ডন প্রাথমিক সমর্থন।

ইতিমধ্যে, পরামর্শ এবং প্রশ্ন সহ Emscripten এবং Dawn সমস্যাগুলির জন্য WebGPU সমস্যাগুলি ফাইল করুন৷

সম্পদ

এই অ্যাপের সোর্স কোড অন্বেষণ করতে নির্দ্বিধায়.

আপনি যদি WebGPU-এর সাহায্যে স্ক্র্যাচ থেকে C++-এ নেটিভ 3D অ্যাপ্লিকেশান তৈরি করতে চান, তাহলে C++ ডকুমেন্টেশন এবং ডন নেটিভ ওয়েবজিপিইউ উদাহরণগুলির জন্য WebGPU জানুন দেখুন।

আপনি যদি মরিচায় আগ্রহী হন তবে আপনি WebGPU এর উপর ভিত্তি করে wgpu গ্রাফিক্স লাইব্রেরিটিও অন্বেষণ করতে পারেন। তাদের হ্যালো-ত্রিভুজ ডেমো দেখুন।

স্বীকৃতি

এই নিবন্ধটি Corentin Wallez , Kai Ninomia , এবং Rachel Andrew দ্বারা পর্যালোচনা করা হয়েছে।

আনস্প্ল্যাশে মার্ক-অলিভিয়ার জোডোইনের ছবি।