Sfoglia il codice sorgente

nacl: the Mandelbrot zoomer is starting to work on NaCl.

legacy
Sam Hocevar sam 13 anni fa
parent
commit
6743bb45cd
14 ha cambiato i file con 534 aggiunte e 15 eliminazioni
  1. +10
    -0
      build-nacl32
  2. +0
    -0
      build-nacl64
  3. +5
    -2
      build/lol-build
  4. +8
    -0
      configure.ac
  5. +7
    -1
      src/Makefile.am
  6. +118
    -0
      src/platform/nacl/nacl_instance.cpp
  7. +53
    -0
      src/platform/nacl/nacl_instance.h
  8. +60
    -0
      src/platform/nacl/nacl_module.cpp
  9. +3
    -3
      src/platform/nacl/naclapp.cpp
  10. +90
    -0
      src/platform/nacl/opengl_context.cpp
  11. +94
    -0
      src/platform/nacl/opengl_context.h
  12. +22
    -0
      src/platform/nacl/opengl_context_ptrs.h
  13. +4
    -0
      test/tutorial/tut01.cpp
  14. +60
    -9
      test/tutorial/tut03.cpp

+ 10
- 0
build-nacl32 Vedi File

@@ -0,0 +1,10 @@
#!/bin/sh

# This can't hurt
make distclean

set -e
./build/lol-build bootstrap nacl-i386
./build/lol-build configure nacl-i386
./build/lol-build build nacl-i386


build-nacl → build-nacl64 Vedi File


+ 5
- 2
build/lol-build Vedi File

@@ -15,6 +15,7 @@
# And <platform> is one of:
# - linux-i386
# - linux-amd64
# - nacl-i386
# - nacl-amd64
# - ios-arm
# - osx-amd64
@@ -119,9 +120,11 @@ configure()
cd monsterz/android
android update project --path .
;;
nacl-i386)
./configure CXX=i686-nacl-g++ CC=i686-nacl-gcc ac_cv_exeext=.32.nexe --host=none LOL_LIBS="-lppapi -lppapi_gles2 -lppapi_cpp -u _ZN2pp12CreateModuleEv"
;;
nacl-amd64)
# no need for "-u _ZN2pp12CreateModuleEv" but it could be helpful
./configure CXX=x86_64-nacl-g++ CC=x86_64-nacl-gcc ac_cv_exeext=.nexe --host=none LOL_LIBS="-lppapi -lppapi_gles2 -lppapi_cpp"
./configure CXX=x86_64-nacl-g++ CC=x86_64-nacl-gcc ac_cv_exeext=.64.nexe --host=none LOL_LIBS="-lppapi -lppapi_gles2 -lppapi_cpp -u _ZN2pp12CreateModuleEv"
;;
ps3-ppu)
PATH="$PATH" ./configure CXX=ppu-lv2-g++ CC=ppu-lv2-gcc ac_cv_exeext=.elf --host=none


+ 8
- 0
configure.ac Vedi File

@@ -191,6 +191,7 @@ LIBS="${LIBS} ${GL_LIBS} ${GLES2_LIBS}"
AC_CHECK_FUNCS(glBegin)
LIBS="${save_LIBS}"


dnl Use SDL? (always required on Linux or Win32)
ac_cv_my_have_sdl="no"
ac_cv_my_have_sdl_image="no"
@@ -252,6 +253,13 @@ AM_CONDITIONAL(USE_SDL_MIXER, test "${ac_cv_my_have_sdl_mixer}" = "yes")
AM_CONDITIONAL(USE_SDL_IMAGE, test "${ac_cv_my_have_sdl_image}" = "yes")


dnl Use NativeClient?
ac_cv_my_have_nacl="no"
AC_LANG_PUSH(C++)
AC_CHECK_HEADERS(ppapi/cpp/instance.h, [ac_cv_my_have_nacl="yes"])
AC_LANG_POP(C++)
AM_CONDITIONAL(USE_NACL, test "${ac_cv_my_have_nacl}" != "no")

dnl Use EGL?
ac_cv_my_have_egl="no"
PKG_CHECK_MODULES(EGL, egl, [ac_cv_my_have_egl="yes"], [:])


+ 7
- 1
src/Makefile.am Vedi File

@@ -43,8 +43,14 @@ sdl_sources = \
platform/sdl/sdlapp.cpp platform/sdl/sdlapp.h \
platform/sdl/sdlinput.cpp platform/sdl/sdlinput.h

if USE_NACL
nacl_sources = \
platform/nacl/naclapp.cpp platform/nacl/naclapp.h
platform/nacl/naclapp.cpp platform/nacl/naclapp.h \
platform/nacl/nacl_instance.cpp platform/nacl/nacl_instance.h \
platform/nacl/nacl_module.cpp \
platform/nacl/opengl_context.cpp platform/nacl/opengl_context.h \
platform/nacl/opengl_context_ptrs.h
endif

if HAVE_PS3
ps3_sources = \


+ 118
- 0
src/platform/nacl/nacl_instance.cpp Vedi File

@@ -0,0 +1,118 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#if defined HAVE_CONFIG_H
# include "config.h"
#endif

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

#include <ppapi/cpp/rect.h>
#include <ppapi/cpp/size.h>
#include <ppapi/cpp/var.h>
#include <ppapi/cpp/module.h>
#include <ppapi/cpp/completion_callback.h>

#include "core.h"
#include "debug/quad.h"

#include "platform/nacl/nacl_instance.h"
#include "platform/nacl/opengl_context.h"

namespace lol
{

NaClInstance::NaClInstance(PP_Instance instance)
: pp::Instance(instance),
m_size(0, 0)
{
;
}

NaClInstance::~NaClInstance()
{
// Destroy the cube view while GL context is current.
opengl_context_->MakeContextCurrent(this);
}

static double const DELTA_MS = 1000.0 / 60.0;

void TickCallback(void* data, int32_t result)
{
NaClInstance *instance = (NaClInstance *)data;
instance->DrawSelf();

/* FIXME: only set if if Ticker isn't finished */
pp::Module::Get()->core()->CallOnMainThread(
DELTA_MS, pp::CompletionCallback(&TickCallback, data), PP_OK);
}

}
#define main OLDMAIN
#include "../test/tutorial/tut03.cpp"
#undef main
namespace lol {

bool NaClInstance::Init(uint32_t /* argc */,
const char* /* argn */[],
const char* /* argv */[])
{
Ticker::Setup(60.0f);

//new Kub();
//new DebugQuad();
new Fractal(ivec2(640, 480));

// My timer callback
pp::Module::Get()->core()->CallOnMainThread(
DELTA_MS, pp::CompletionCallback(&TickCallback, this), PP_OK);

return true;
}

void NaClInstance::HandleMessage(const pp::Var& message)
{
if (!message.is_string())
return;
/* FIXME: do some shit here */
}

void NaClInstance::DidChangeView(const pp::Rect& position, const pp::Rect& clip)
{
if (position.size().width() == m_size.x &&
position.size().height() == m_size.y)
return; // Size didn't change, no need to update anything.

m_size = ivec2(position.size().width(), position.size().height());

if (opengl_context_ == NULL)
opengl_context_.reset(new OpenGLContext(this));
opengl_context_->InvalidateContext(this);
opengl_context_->ResizeContext(position.size());
if (!opengl_context_->MakeContextCurrent(this))
return;

Video::Setup(m_size);
DrawSelf();
}

void NaClInstance::DrawSelf()
{
if (opengl_context_ == NULL)
return;

Ticker::ClampFps();
Ticker::TickGame();

opengl_context_->MakeContextCurrent(this);
Ticker::TickDraw();
opengl_context_->FlushContext();
}

} // namespace lol


+ 53
- 0
src/platform/nacl/nacl_instance.h Vedi File

@@ -0,0 +1,53 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXAMPLES_TUMBLER_TUMBLER_H_
#define EXAMPLES_TUMBLER_TUMBLER_H_

#include <pthread.h>
#include <map>
#include <vector>

#include <ppapi/cpp/instance.h>

#include "platform/nacl/opengl_context.h"
#include "platform/nacl/opengl_context_ptrs.h"

namespace lol {

class NaClInstance : public pp::Instance {
public:
explicit NaClInstance(PP_Instance instance);

// The dtor makes the 3D context current before deleting the cube view, then
// destroys the 3D context both in the module and in the browser.
virtual ~NaClInstance();

// Called by the browser when the NaCl module is loaded and all ready to go.
virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);

// Called whenever the in-browser window changes size.
virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);

// Called by the browser to handle the postMessage() call in Javascript.
virtual void HandleMessage(const pp::Var& message);

// Bind and publish the module's methods to JavaScript.
//void InitializeMethods(ScriptingBridge* bridge);

// Called to draw the contents of the module's browser area.
void DrawSelf();

// private:
// Browser connectivity and scripting support.
// ScriptingBridge scripting_bridge_;

SharedOpenGLContext opengl_context_;

ivec2 m_size;
};

} // namespace lol

#endif // EXAMPLES_TUMBLER_TUMBLER_H_

+ 60
- 0
src/platform/nacl/nacl_module.cpp Vedi File

@@ -0,0 +1,60 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#if defined HAVE_CONFIG_H
# include "config.h"
#endif

#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/module.h>
#include <ppapi/gles2/gl2ext_ppapi.h>

#include "core.h"
#include "lolgl.h"

#include "platform/nacl/nacl_instance.h"

/// The Module class. The browser calls the CreateInstance() method to create
/// an instance of your NaCl module on the web page. The browser creates a new
/// instance for each <embed> tag with type="application/x-nacl".
class NaClModule : public pp::Module
{
public:
NaClModule() : pp::Module() {}
virtual ~NaClModule()
{
glTerminatePPAPI();
}

/// Called by the browser when the module is first loaded and ready to run.
/// This is called once per module, not once per instance of the module on
/// the page.
virtual bool Init()
{
return glInitializePPAPI(get_browser_interface()) == GL_TRUE;
}

/// Create and return a Lol instance object.
/// @param[in] instance The browser-side instance.
/// @return the plugin-side instance.
virtual pp::Instance* CreateInstance(PP_Instance instance)
{
return new lol::NaClInstance(instance);
}
};

namespace pp
{
/// Factory function called by the browser when the module is first loaded.
/// The browser keeps a singleton of this module. It calls the
/// CreateInstance() method on the object you return to make instances. There
/// is one instance per <embed> tag on the page. This is the main binding
/// point for your NaCl module with the browser.
Module* CreateModule()
{
return new NaClModule();
}

} // namespace pp


+ 3
- 3
src/platform/nacl/naclapp.cpp Vedi File

@@ -13,9 +13,9 @@
#endif

#if defined __native_client__
# include "ppapi/cpp/instance.h"
# include "ppapi/cpp/module.h"
# include "ppapi/cpp/var.h"
# include <ppapi/cpp/instance.h>
# include <ppapi/cpp/module.h>
# include <ppapi/cpp/var.h>
#endif

#include "core.h"


+ 90
- 0
src/platform/nacl/opengl_context.cpp Vedi File

@@ -0,0 +1,90 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#if defined HAVE_CONFIG_H
# include "config.h"
#endif

#include <pthread.h>
#include <ppapi/cpp/completion_callback.h>
#include <ppapi/gles2/gl2ext_ppapi.h>

#include "core.h"

#include "platform/nacl/opengl_context.h"

namespace {
// This is called by the brower when the 3D context has been flushed to the
// browser window.
void FlushCallback(void* data, int32_t result) {
static_cast<lol::OpenGLContext*>(data)->set_flush_pending(false);
}
} // namespace

namespace lol {

OpenGLContext::OpenGLContext(pp::Instance* instance)
: pp::Graphics3DClient(instance),
flush_pending_(false) {
pp::Module* module = pp::Module::Get();
assert(module);
gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>(
module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE));
assert(gles2_interface_);
}

OpenGLContext::~OpenGLContext() {
glSetCurrentContextPPAPI(0);
}

bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) {
if (instance == NULL) {
glSetCurrentContextPPAPI(0);
return false;
}
// Lazily create the Pepper context.
if (context_.is_null()) {
int32_t attribs[] = {
PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8,
PP_GRAPHICS3DATTRIB_SAMPLES, 0,
PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
PP_GRAPHICS3DATTRIB_WIDTH, size_.width(),
PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(),
PP_GRAPHICS3DATTRIB_NONE
};
context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs);
if (context_.is_null()) {
glSetCurrentContextPPAPI(0);
return false;
}
instance->BindGraphics(context_);
}
glSetCurrentContextPPAPI(context_.pp_resource());
return true;
}

void OpenGLContext::InvalidateContext(pp::Instance* instance) {
glSetCurrentContextPPAPI(0);
}

void OpenGLContext::ResizeContext(const pp::Size& size) {
size_ = size;
if (!context_.is_null()) {
context_.ResizeBuffers(size.width(), size.height());
}
}


void OpenGLContext::FlushContext() {
if (flush_pending()) {
// A flush is pending so do nothing; just drop this flush on the floor.
return;
}
set_flush_pending(true);
context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this));
}
} // namespace lol


+ 94
- 0
src/platform/nacl/opengl_context.h Vedi File

@@ -0,0 +1,94 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_

///
/// @file
/// OpenGLContext manages the OpenGL context in the browser that is associated
/// with a @a pp::Instance instance.
///

#include <assert.h>
#include <pthread.h>

#include <algorithm>
#include <string>

#include <ppapi/c/ppb_opengles2.h>
#include <ppapi/cpp/graphics_3d_client.h>
#include <ppapi/cpp/graphics_3d.h>
#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/size.h>

#include "platform/nacl/opengl_context_ptrs.h"

namespace lol {

/// OpenGLContext manages an OpenGL rendering context in the browser.
///
class OpenGLContext : public pp::Graphics3DClient {
public:
explicit OpenGLContext(pp::Instance* instance);

/// Release all the in-browser resources used by this context, and make this
/// context invalid.
virtual ~OpenGLContext();

/// The Graphics3DClient interfcace.
virtual void Graphics3DContextLost() {
assert(!"Unexpectedly lost graphics context");
}

/// Make @a this the current 3D context in @a instance.
/// @param instance The instance of the NaCl module that will receive the
/// the current 3D context.
/// @return success.
bool MakeContextCurrent(pp::Instance* instance);

/// Flush the contents of this context to the browser's 3D device.
void FlushContext();

/// Make the underlying 3D device invalid, so that any subsequent rendering
/// commands will have no effect. The next call to MakeContextCurrent() will
/// cause the underlying 3D device to get rebound and start receiving
/// receiving rendering commands again. Use InvalidateContext(), for
/// example, when resizing the context's viewing area.
void InvalidateContext(pp::Instance* instance);

/// Resize the context.
void ResizeContext(const pp::Size& size);

/// The OpenGL ES 2.0 interface.
const struct PPB_OpenGLES2* gles2() const {
return gles2_interface_;
}

/// The PP_Resource needed to make GLES2 calls through the Pepper interface.
const PP_Resource gl_context() const {
return context_.pp_resource();
}

/// Indicate whether a flush is pending. This can only be called from the
/// main thread; it is not thread safe.
bool flush_pending() const {
return flush_pending_;
}
void set_flush_pending(bool flag) {
flush_pending_ = flag;
}

private:
pp::Size size_;
pp::Graphics3D context_;
bool flush_pending_;

const struct PPB_OpenGLES2* gles2_interface_;
};

} // namespace lol

#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_


+ 22
- 0
src/platform/nacl/opengl_context_ptrs.h Vedi File

@@ -0,0 +1,22 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_

// A convenience wrapper for a shared OpenGLContext pointer type. As other
// smart pointer types are needed, add them here.

#include <tr1/memory>

namespace lol {

class OpenGLContext;

typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext;

} // namespace lol

#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_


+ 4
- 0
test/tutorial/tut01.cpp Vedi File

@@ -23,6 +23,10 @@ using namespace lol;
# include <SDL_main.h>
#endif

#if defined __native_client__
# define main old_main
#endif

#if defined _WIN32
# undef main /* FIXME: still needed? */
#endif


+ 60
- 9
test/tutorial/tut03.cpp Vedi File

@@ -59,7 +59,12 @@ public:

/* Window size decides the world aspect ratio. For instance, 640×480
* will be mapped to (-0.66,-0.5) - (0.66,0.5). */
#if !defined __native_client__
m_window_size = Video::GetSize();
#else
/* FIXME: it's illegal to call this on the game thread! */
m_window_size = ivec2(640, 480);
#endif
if (m_window_size.y < m_window_size.x)
m_window2world = 0.5 / m_window_size.y;
else
@@ -76,7 +81,11 @@ public:
m_dirty[i] = 2;
}
m_center = -0.75;
#if defined __CELLOS_LV2__ || defined __native_client__
m_zoom_speed = -0.0025;
#else
m_zoom_speed = 0.0;
#endif
m_radius = 5.0;
m_ready = false;

@@ -100,9 +109,14 @@ public:
uint8_t red = r * 255.99f;
uint8_t green = g * 255.99f;
uint8_t blue = b * 255.99f;
m_palette[i] = u8vec4(blue, green, red, 0);
#if defined __native_client__
m_palette[i] = u8vec4(red, green, blue, 255);
#else
m_palette[i] = u8vec4(blue, green, red, 255);
#endif
}

#if !defined __native_client__
m_centertext = new Text(NULL, "gfx/font/ascii.png");
m_centertext->SetPos(ivec3(5, m_window_size.y - 15, 1));
Ticker::Ref(m_centertext);
@@ -114,6 +128,7 @@ public:
m_zoomtext = new Text(NULL, "gfx/font/ascii.png");
m_zoomtext->SetPos(ivec3(5, m_window_size.y - 43, 1));
Ticker::Ref(m_zoomtext);
#endif

position = ivec3(0, 0, 0);
bbox[0] = position;
@@ -124,9 +139,11 @@ public:
~Fractal()
{
Input::UntrackMouse(this);
#if !defined __native_client__
Ticker::Unref(m_centertext);
Ticker::Unref(m_mousetext);
Ticker::Unref(m_zoomtext);
#endif
delete m_pixels;
delete m_tmppixels;
delete m_palette;
@@ -158,9 +175,7 @@ public:
f64cmplx worldmouse = m_center + ScreenToWorldOffset(mousepos);

ivec3 buttons = Input::GetMouseButtons();
#ifdef __CELLOS_LV2__
m_zoom_speed = 0.0005;
#else
#if !defined __CELLOS_LV2__ && !defined __native_client__
if ((buttons[0] || buttons[2]) && mousepos.x != -1)
{
double zoom = buttons[0] ? -0.0005 : 0.0005;
@@ -182,13 +197,20 @@ public:
double oldradius = m_radius;
double zoom = pow(2.0, deltams * m_zoom_speed);
if (m_radius * zoom > 8.0)
{
m_zoom_speed *= -1.0;
zoom = 8.0 / m_radius;
}
else if (m_radius * zoom < 1e-14)
{
m_zoom_speed *= -1.0;
zoom = 1e-14 / m_radius;
}
m_radius *= zoom;
#ifdef __CELLOS_LV2__
m_center = f64cmplx(-.22815528839841, -1.11514249704382);
#if defined __CELLOS_LV2__ || defined __native_client__
//m_center = f64cmplx(-.22815528839841, -1.11514249704382);
//m_center = f64cmplx(0.001643721971153, 0.822467633298876);
m_center = f64cmplx(-0.65823419062254, .50221777363480);
#else
m_center = (m_center - worldmouse) * zoom + worldmouse;
worldmouse = m_center + ScreenToWorldOffset(mousepos);
@@ -230,6 +252,7 @@ public:
m_zoom_settings[cur_index][2] = m_zoom_settings[prev_index][2] * m_deltascale[cur_index];
}

#if !defined __native_client__
char buf[128];
sprintf(buf, "center: %+16.14f%+16.14fi", m_center.x, m_center.y);
m_centertext->SetText(buf);
@@ -237,6 +260,7 @@ public:
m_mousetext->SetText(buf);
sprintf(buf, " zoom: %g", 1.0 / m_radius);
m_zoomtext->SetText(buf);
#endif

u8vec4 *m_pixelstart = m_pixels + m_size.x * m_size.y / 4 * m_frame;

@@ -284,7 +308,7 @@ public:
}
else
{
*m_pixelstart++ = u8vec4(0, 0, 0, 0);
*m_pixelstart++ = u8vec4(0, 0, 0, 255);
}
}
}
@@ -334,16 +358,35 @@ public:

m_shader = Shader::Create(
#if !defined __CELLOS_LV2__
#if !defined HAVE_GLES_2X
"#version 120\n"
"attribute vec2 in_TexCoord;\n"
#else
"precision highp float;"
#endif
""
#if defined HAVE_GLES_2X
"varying vec2 pass_TexCoord;"
#endif
"attribute vec2 in_TexCoord;"
"attribute vec2 in_Vertex;"
"void main(void) {"
" gl_Position = vec4(in_Vertex, 0.0, 1.0);"
" gl_TexCoord[0] = vec4(in_TexCoord, 0.0, 0.0);\n"
#if defined HAVE_GLES_2X
" pass_TexCoord = in_TexCoord;"
#else
" gl_TexCoord[0] = vec4(in_TexCoord, 0.0, 0.0);"
#endif
"}",

#if !defined HAVE_GLES_2X
"#version 120\n"
#else
"precision highp float;"
#endif
""
#if defined HAVE_GLES_2X
"varying vec2 pass_TexCoord;"
#endif
"uniform vec4 in_TexelSize;"
"uniform mat4 in_ZoomSettings;"
"uniform sampler2D in_Texture;"
@@ -411,7 +454,11 @@ public:
"}"
""
"void main(void) {"
#if defined HAVE_GLES_2X
" vec2 coord = pass_TexCoord;"
#else
" vec2 coord = gl_TexCoord[0].xy;"
#endif
/* Slightly shift our pixel so that it does not lie at
* an exact texel boundary. This would lead to visual
* artifacts. */
@@ -513,7 +560,9 @@ public:
/* FIXME: this object never cleans up */
}

#if !defined HAVE_GLES_2X
glEnable(GL_TEXTURE_2D);
#endif
glBindTexture(GL_TEXTURE_2D, m_texid);

if (m_dirty[m_frame])
@@ -598,7 +647,9 @@ private:
double m_deltascale[4];

/* Debug information */
#if !defined __native_client__
Text *m_centertext, *m_mousetext, *m_zoomtext;
#endif
};

int main(int argc, char **argv)


Caricamento…
Annulla
Salva