Commit 79808322 authored by wuchengli's avatar wuchengli Committed by Commit bot

Add VEA supported profiles to GPUInfo.

Originally we use a static GetSupportedProfiles for each VEA
implementation to get a list of supported codec profiles. But
different devices of the same platform can support different
profiles. To return a correct list, they are dynamically
detected in runtime and added to GPUInfo.

BUG=350197
TEST=Run apprtc loopback and Hangout on peach pit.
Run gpu_info_unittest.

Review URL: https://codereview.chromium.org/568413002

Cr-Commit-Position: refs/heads/master@{#296147}
parent 7f02085d
......@@ -66,6 +66,10 @@ class AuxGPUInfoEnumerator : public gpu::GPUInfo::Enumerator {
virtual void EndGPUDevice() OVERRIDE {
}
virtual void BeginVideoEncodeAcceleratorSupportedProfile() OVERRIDE {}
virtual void EndVideoEncodeAcceleratorSupportedProfile() OVERRIDE {}
virtual void BeginAuxAttributes() OVERRIDE {
in_aux_attributes_ = true;
}
......
......@@ -39,12 +39,6 @@ GpuVideoEncodeAcceleratorHost::~GpuVideoEncodeAcceleratorHost() {
impl_->RemoveDeletionObserver(this);
}
// static
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GpuVideoEncodeAcceleratorHost::GetSupportedProfiles() {
return GpuVideoEncodeAccelerator::GetSupportedProfiles();
}
bool GpuVideoEncodeAcceleratorHost::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
......@@ -75,6 +69,14 @@ void GpuVideoEncodeAcceleratorHost::OnChannelError() {
NOTIFY_ERROR(kPlatformFailureError) << "OnChannelError()";
}
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GpuVideoEncodeAcceleratorHost::GetSupportedProfiles() {
DCHECK(CalledOnValidThread());
if (!channel_)
return std::vector<media::VideoEncodeAccelerator::SupportedProfile>();
return channel_->gpu_info().video_encode_accelerator_supported_profiles;
}
bool GpuVideoEncodeAcceleratorHost::Initialize(
media::VideoFrame::Format input_format,
const gfx::Size& input_visible_size,
......
......@@ -44,15 +44,12 @@ class GpuVideoEncodeAcceleratorHost
GpuVideoEncodeAcceleratorHost(GpuChannelHost* channel,
CommandBufferProxyImpl* impl);
// Static query for the supported profiles. This query proxies to
// GpuVideoEncodeAccelerator::GetSupportedProfiles().
static std::vector<SupportedProfile> GetSupportedProfiles();
// IPC::Listener implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
virtual void OnChannelError() OVERRIDE;
// media::VideoEncodeAccelerator implementation.
virtual std::vector<SupportedProfile> GetSupportedProfiles() OVERRIDE;
virtual bool Initialize(media::VideoFrame::Format input_format,
const gfx::Size& input_visible_size,
media::VideoCodecProfile output_profile,
......
......@@ -157,6 +157,13 @@ IPC_STRUCT_TRAITS_BEGIN(gpu::GPUInfo::GPUDevice)
IPC_STRUCT_TRAITS_MEMBER(device_string)
IPC_STRUCT_TRAITS_END()
IPC_STRUCT_TRAITS_BEGIN(media::VideoEncodeAccelerator::SupportedProfile)
IPC_STRUCT_TRAITS_MEMBER(profile)
IPC_STRUCT_TRAITS_MEMBER(max_resolution)
IPC_STRUCT_TRAITS_MEMBER(max_framerate_numerator)
IPC_STRUCT_TRAITS_MEMBER(max_framerate_denominator)
IPC_STRUCT_TRAITS_END()
IPC_STRUCT_TRAITS_BEGIN(gpu::GPUInfo)
IPC_STRUCT_TRAITS_MEMBER(initialization_time)
IPC_STRUCT_TRAITS_MEMBER(optimus)
......@@ -192,6 +199,7 @@ IPC_STRUCT_TRAITS_BEGIN(gpu::GPUInfo)
IPC_STRUCT_TRAITS_MEMBER(dx_diagnostics_info_state)
IPC_STRUCT_TRAITS_MEMBER(dx_diagnostics)
#endif
IPC_STRUCT_TRAITS_MEMBER(video_encode_accelerator_supported_profiles)
IPC_STRUCT_TRAITS_END()
IPC_STRUCT_TRAITS_BEGIN(gpu::Capabilities)
......
......@@ -77,7 +77,6 @@ AndroidVideoEncodeAccelerator::~AndroidVideoEncodeAccelerator() {
DCHECK(thread_checker_.CalledOnValidThread());
}
// static
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
AndroidVideoEncodeAccelerator::GetSupportedProfiles() {
std::vector<MediaCodecBridge::CodecsInfo> codecs_info =
......@@ -105,8 +104,8 @@ AndroidVideoEncodeAccelerator::GetSupportedProfiles() {
// encoder? Sure would be. Too bad it doesn't. So we hard-code some
// reasonable defaults.
profile.max_resolution.SetSize(1920, 1088);
profile.max_framerate.numerator = 30;
profile.max_framerate.denominator = 1;
profile.max_framerate_numerator = 30;
profile.max_framerate_denominator = 1;
profiles.push_back(profile);
}
return profiles;
......
......@@ -34,10 +34,9 @@ class CONTENT_EXPORT AndroidVideoEncodeAccelerator
AndroidVideoEncodeAccelerator();
virtual ~AndroidVideoEncodeAccelerator();
static std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles();
// media::VideoEncodeAccelerator implementation.
virtual std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles() OVERRIDE;
virtual bool Initialize(media::VideoFrame::Format format,
const gfx::Size& input_visible_size,
media::VideoCodecProfile output_profile,
......
......@@ -95,7 +95,7 @@ void GpuVideoEncodeAccelerator::Initialize(
return;
}
CreateEncoder();
encoder_ = CreateEncoder();
if (!encoder_) {
DLOG(ERROR)
<< "GpuVideoEncodeAccelerator::Initialize(): VEA creation failed";
......@@ -165,39 +165,29 @@ void GpuVideoEncodeAccelerator::OnWillDestroyStub() {
// static
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GpuVideoEncodeAccelerator::GetSupportedProfiles() {
std::vector<media::VideoEncodeAccelerator::SupportedProfile> profiles;
#if defined(OS_CHROMEOS) && defined(USE_X11)
#if defined(ARCH_CPU_ARMEL)
profiles = V4L2VideoEncodeAccelerator::GetSupportedProfiles();
#elif defined(ARCH_CPU_X86_FAMILY)
profiles = VaapiVideoEncodeAccelerator::GetSupportedProfiles();
#endif
#elif defined(OS_ANDROID) && defined(ENABLE_WEBRTC)
profiles = AndroidVideoEncodeAccelerator::GetSupportedProfiles();
#endif
// TODO(sheu): return platform-specific profiles.
return profiles;
scoped_ptr<media::VideoEncodeAccelerator> encoder = CreateEncoder();
if (!encoder)
return std::vector<media::VideoEncodeAccelerator::SupportedProfile>();
return encoder->GetSupportedProfiles();
}
void GpuVideoEncodeAccelerator::CreateEncoder() {
DCHECK(!encoder_);
scoped_ptr<media::VideoEncodeAccelerator>
GpuVideoEncodeAccelerator::CreateEncoder() {
scoped_ptr<media::VideoEncodeAccelerator> encoder;
#if defined(OS_CHROMEOS) && defined(USE_X11)
#if defined(ARCH_CPU_ARMEL)
scoped_ptr<V4L2Device> device = V4L2Device::Create(V4L2Device::kEncoder);
if (!device.get())
return;
encoder_.reset(new V4L2VideoEncodeAccelerator(device.Pass()));
if (device)
encoder.reset(new V4L2VideoEncodeAccelerator(device.Pass()));
#elif defined(ARCH_CPU_X86_FAMILY)
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (!cmd_line->HasSwitch(switches::kDisableVaapiAcceleratedVideoEncode))
encoder_.reset(new VaapiVideoEncodeAccelerator(gfx::GetXDisplay()));
encoder.reset(new VaapiVideoEncodeAccelerator(gfx::GetXDisplay()));
#endif
#elif defined(OS_ANDROID) && defined(ENABLE_WEBRTC)
encoder_.reset(new AndroidVideoEncodeAccelerator());
encoder.reset(new AndroidVideoEncodeAccelerator());
#endif
return encoder.Pass();
}
void GpuVideoEncodeAccelerator::OnEncode(int32 frame_id,
......
......@@ -63,7 +63,7 @@ class GpuVideoEncodeAccelerator
private:
// Create the appropriate platform-specific VEA.
void CreateEncoder();
static scoped_ptr<media::VideoEncodeAccelerator> CreateEncoder();
// IPC handlers, proxying media::VideoEncodeAccelerator for the renderer
// process.
......
......@@ -280,7 +280,6 @@ void V4L2VideoEncodeAccelerator::Destroy() {
delete this;
}
// static
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
V4L2VideoEncodeAccelerator::GetSupportedProfiles() {
std::vector<SupportedProfile> profiles;
......@@ -290,14 +289,14 @@ V4L2VideoEncodeAccelerator::GetSupportedProfiles() {
if (cmd_line->HasSwitch(switches::kEnableWebRtcHWVp8Encoding)) {
profile.profile = media::VP8PROFILE_ANY;
profile.max_resolution.SetSize(1920, 1088);
profile.max_framerate.numerator = 30;
profile.max_framerate.denominator = 1;
profile.max_framerate_numerator = 30;
profile.max_framerate_denominator = 1;
profiles.push_back(profile);
} else {
profile.profile = media::H264PROFILE_MAIN;
profile.max_resolution.SetSize(1920, 1088);
profile.max_framerate.numerator = 30;
profile.max_framerate.denominator = 1;
profile.max_framerate_numerator = 30;
profile.max_framerate_denominator = 1;
profiles.push_back(profile);
}
......
......@@ -46,6 +46,8 @@ class CONTENT_EXPORT V4L2VideoEncodeAccelerator
virtual ~V4L2VideoEncodeAccelerator();
// media::VideoEncodeAccelerator implementation.
virtual std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles() OVERRIDE;
virtual bool Initialize(media::VideoFrame::Format format,
const gfx::Size& input_visible_size,
media::VideoCodecProfile output_profile,
......@@ -59,9 +61,6 @@ class CONTENT_EXPORT V4L2VideoEncodeAccelerator
uint32 framerate) OVERRIDE;
virtual void Destroy() OVERRIDE;
static std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles();
private:
// Auto-destroy reference for BitstreamBuffer, for tracking buffers passed to
// this instance.
......
......@@ -104,7 +104,6 @@ struct VaapiVideoEncodeAccelerator::BitstreamBufferRef {
const size_t size;
};
// static
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
VaapiVideoEncodeAccelerator::GetSupportedProfiles() {
std::vector<SupportedProfile> profiles;
......@@ -116,8 +115,8 @@ VaapiVideoEncodeAccelerator::GetSupportedProfiles() {
SupportedProfile profile;
profile.profile = media::H264PROFILE_MAIN;
profile.max_resolution.SetSize(1920, 1088);
profile.max_framerate.numerator = kDefaultFramerate;
profile.max_framerate.denominator = 1;
profile.max_framerate_numerator = kDefaultFramerate;
profile.max_framerate_denominator = 1;
profiles.push_back(profile);
// This is actually only constrained (see crbug.com/345569).
......
......@@ -29,6 +29,8 @@ class CONTENT_EXPORT VaapiVideoEncodeAccelerator
virtual ~VaapiVideoEncodeAccelerator();
// media::VideoEncodeAccelerator implementation.
virtual std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles() OVERRIDE;
virtual bool Initialize(media::VideoFrame::Format format,
const gfx::Size& input_visible_size,
media::VideoCodecProfile output_profile,
......@@ -42,9 +44,6 @@ class CONTENT_EXPORT VaapiVideoEncodeAccelerator
uint32 framerate) OVERRIDE;
virtual void Destroy() OVERRIDE;
static std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles();
private:
// Reference picture list.
typedef std::list<scoped_refptr<VASurface> > RefPicList;
......
......@@ -22,6 +22,7 @@
#include "content/common/content_constants_internal.h"
#include "content/common/gpu/gpu_config.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/gpu/media/gpu_video_encode_accelerator.h"
#include "content/common/sandbox_linux/sandbox_linux.h"
#include "content/gpu/gpu_child_thread.h"
#include "content/gpu/gpu_process.h"
......@@ -328,6 +329,9 @@ int GpuMain(const MainFunctionParams& parameters) {
#elif defined(OS_MACOSX)
gpu_info.sandboxed = Sandbox::SandboxIsCurrentlyActive();
#endif
gpu_info.video_encode_accelerator_supported_profiles =
content::GpuVideoEncodeAccelerator::GetSupportedProfiles();
} else {
dead_on_arrival = true;
}
......
......@@ -35,7 +35,11 @@ void CreateVideoEncodeAccelerator(
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetSupportedVideoEncodeAcceleratorProfiles() {
return GpuVideoEncodeAcceleratorHost::GetSupportedProfiles();
scoped_refptr<media::GpuVideoAcceleratorFactories> gpu_factories =
RenderThreadImpl::current()->GetGpuFactories();
if (!gpu_factories.get())
return std::vector<media::VideoEncodeAccelerator::SupportedProfile>();
return gpu_factories->GetVideoEncodeAcceleratorSupportedProfiles();
}
} // namespace content
......@@ -245,4 +245,11 @@ RendererGpuVideoAcceleratorFactories::GetTaskRunner() {
return task_runner_;
}
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
RendererGpuVideoAcceleratorFactories::
GetVideoEncodeAcceleratorSupportedProfiles() {
return gpu_channel_host_->gpu_info()
.video_encode_accelerator_supported_profiles;
}
} // namespace content
......@@ -63,6 +63,8 @@ class CONTENT_EXPORT RendererGpuVideoAcceleratorFactories
const SkBitmap& pixels) OVERRIDE;
virtual base::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
virtual scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() OVERRIDE;
virtual std::vector<media::VideoEncodeAccelerator::SupportedProfile>
GetVideoEncodeAcceleratorSupportedProfiles() OVERRIDE;
private:
friend class base::RefCountedThreadSafe<RendererGpuVideoAcceleratorFactories>;
......
......@@ -22,8 +22,8 @@ void VEAToWebRTCCodecs(
const media::VideoEncodeAccelerator::SupportedProfile& profile) {
int width = profile.max_resolution.width();
int height = profile.max_resolution.height();
int fps = profile.max_framerate.numerator;
DCHECK_EQ(profile.max_framerate.denominator, 1U);
int fps = profile.max_framerate_numerator;
DCHECK_EQ(profile.max_framerate_denominator, 1U);
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (profile.profile >= media::VP8PROFILE_MIN &&
......@@ -64,9 +64,8 @@ media::VideoCodecProfile WebRTCCodecToVideoCodecProfile(
RTCVideoEncoderFactory::RTCVideoEncoderFactory(
const scoped_refptr<media::GpuVideoAcceleratorFactories>& gpu_factories)
: gpu_factories_(gpu_factories) {
// Query media::VideoEncodeAccelerator (statically) for our supported codecs.
std::vector<media::VideoEncodeAccelerator::SupportedProfile> profiles =
GpuVideoEncodeAcceleratorHost::GetSupportedProfiles();
gpu_factories_->GetVideoEncodeAcceleratorSupportedProfiles();
for (size_t i = 0; i < profiles.size(); ++i)
VEAToWebRTCCodecs(&codecs_, profiles[i]);
}
......
include_rules = [
"+third_party/libxml", # For parsing WinSAT results files.
"+third_party/libXNVCtrl", # For NV driver version query.
"+media/video", # For VideoEncodeAccelerator::SupportedProfile.
]
......@@ -17,6 +17,19 @@ void EnumerateGPUDevice(gpu::GPUInfo::Enumerator* enumerator,
enumerator->EndGPUDevice();
}
void EnumerateVideoEncodeAcceleratorSupportedProfile(
gpu::GPUInfo::Enumerator* enumerator,
const media::VideoEncodeAccelerator::SupportedProfile profile) {
enumerator->BeginVideoEncodeAcceleratorSupportedProfile();
enumerator->AddInt("profile", profile.profile);
enumerator->AddInt("maxResolutionWidth", profile.max_resolution.width());
enumerator->AddInt("maxResolutionHeight", profile.max_resolution.height());
enumerator->AddInt("maxFramerateNumerator", profile.max_framerate_numerator);
enumerator->AddInt("maxFramerateDenominator",
profile.max_framerate_denominator);
enumerator->EndVideoEncodeAcceleratorSupportedProfile();
}
} // namespace
namespace gpu {
......@@ -88,6 +101,8 @@ void GPUInfo::EnumerateFields(Enumerator* enumerator) const {
CollectInfoResult dx_diagnostics_info_state;
DxDiagNode dx_diagnostics;
#endif
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
video_encode_accelerator_supported_profiles;
};
// If this assert fails then most likely something below needs to be updated.
......@@ -143,6 +158,11 @@ void GPUInfo::EnumerateFields(Enumerator* enumerator) const {
enumerator->AddInt("DxDiagnosticsInfoState", dx_diagnostics_info_state);
#endif
// TODO(kbr): add dx_diagnostics on Windows.
for (size_t ii = 0; ii < video_encode_accelerator_supported_profiles.size();
++ii) {
EnumerateVideoEncodeAcceleratorSupportedProfile(
enumerator, video_encode_accelerator_supported_profiles[ii]);
}
enumerator->EndAuxAttributes();
}
......
......@@ -18,6 +18,7 @@
#include "gpu/config/dx_diag_node.h"
#include "gpu/config/gpu_performance_stats.h"
#include "gpu/gpu_export.h"
#include "media/video/video_encode_accelerator.h"
namespace gpu {
......@@ -176,6 +177,9 @@ struct GPU_EXPORT GPUInfo {
// The information returned by the DirectX Diagnostics Tool.
DxDiagNode dx_diagnostics;
#endif
std::vector<media::VideoEncodeAccelerator::SupportedProfile>
video_encode_accelerator_supported_profiles;
// Note: when adding new members, please remember to update EnumerateFields
// in gpu_info.cc.
......@@ -200,6 +204,11 @@ struct GPU_EXPORT GPUInfo {
virtual void BeginGPUDevice() = 0;
virtual void EndGPUDevice() = 0;
// Markers indicating that a VideoEncodeAccelerator::SupportedProfile is
// being described.
virtual void BeginVideoEncodeAcceleratorSupportedProfile() = 0;
virtual void EndVideoEncodeAcceleratorSupportedProfile() = 0;
// Markers indicating that "auxiliary" attributes of the GPUInfo
// (according to the DevTools protocol) are being described.
virtual void BeginAuxAttributes() = 0;
......
......@@ -156,6 +156,8 @@ void MergeGPUInfoGL(GPUInfo* basic_gpu_info,
basic_gpu_info->direct_rendering = context_gpu_info.direct_rendering;
basic_gpu_info->context_info_state = context_gpu_info.context_info_state;
basic_gpu_info->initialization_time = context_gpu_info.initialization_time;
basic_gpu_info->video_encode_accelerator_supported_profiles =
context_gpu_info.video_encode_accelerator_supported_profiles;
}
} // namespace gpu
......
......@@ -32,6 +32,7 @@ TEST(GPUInfoBasicTest, EmptyGPUInfo) {
#if defined(OS_WIN)
EXPECT_EQ(gpu_info.dx_diagnostics_info_state, kCollectInfoNone);
#endif
EXPECT_EQ(gpu_info.video_encode_accelerator_supported_profiles.size(), 0u);
}
} // namespace gpu
......
......@@ -32,6 +32,11 @@ FakeVideoEncodeAccelerator::~FakeVideoEncodeAccelerator() {
weak_this_factory_.InvalidateWeakPtrs();
}
std::vector<VideoEncodeAccelerator::SupportedProfile>
FakeVideoEncodeAccelerator::GetSupportedProfiles() {
return std::vector<VideoEncodeAccelerator::SupportedProfile>();
}
bool FakeVideoEncodeAccelerator::Initialize(
media::VideoFrame::Format input_format,
const gfx::Size& input_visible_size,
......
......@@ -28,6 +28,8 @@ class FakeVideoEncodeAccelerator : public VideoEncodeAccelerator {
std::vector<uint32>* stored_bitrates);
virtual ~FakeVideoEncodeAccelerator();
virtual std::vector<VideoEncodeAccelerator::SupportedProfile>
GetSupportedProfiles() OVERRIDE;
virtual bool Initialize(media::VideoFrame::Format input_format,
const gfx::Size& input_visible_size,
VideoCodecProfile output_profile,
......
......@@ -11,6 +11,7 @@
#include "base/memory/scoped_ptr.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "media/base/media_export.h"
#include "media/video/video_encode_accelerator.h"
class SkBitmap;
......@@ -27,7 +28,6 @@ class Size;
namespace media {
class VideoDecodeAccelerator;
class VideoEncodeAccelerator;
// Helper interface for specifying factories needed to instantiate a hardware
// video accelerator.
......@@ -73,6 +73,10 @@ class MEDIA_EXPORT GpuVideoAcceleratorFactories
// Returns the task runner the video accelerator runs on.
virtual scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() = 0;
// Returns the supported codec profiles of video encode accelerator.
virtual std::vector<VideoEncodeAccelerator::SupportedProfile>
GetVideoEncodeAcceleratorSupportedProfiles() = 0;
protected:
friend class base::RefCountedThreadSafe<GpuVideoAcceleratorFactories>;
virtual ~GpuVideoAcceleratorFactories();
......
......@@ -45,6 +45,8 @@ class MockGpuVideoAcceleratorFactories : public GpuVideoAcceleratorFactories {
const SkBitmap& pixels));
MOCK_METHOD1(CreateSharedMemory, base::SharedMemory*(size_t size));
MOCK_METHOD0(GetTaskRunner, scoped_refptr<base::SingleThreadTaskRunner>());
MOCK_METHOD0(GetVideoEncodeAcceleratorSupportedProfiles,
std::vector<VideoEncodeAccelerator::SupportedProfile>());
virtual scoped_ptr<VideoDecodeAccelerator> CreateVideoDecodeAccelerator()
OVERRIDE;
......
......@@ -26,10 +26,8 @@ class MEDIA_EXPORT VideoEncodeAccelerator {
struct SupportedProfile {
VideoCodecProfile profile;
gfx::Size max_resolution;
struct {
uint32 numerator;
uint32 denominator;
} max_framerate;
uint32 max_framerate_numerator;
uint32 max_framerate_denominator;
};
// Enumeration of potential errors generated by the API.
......@@ -92,6 +90,10 @@ class MEDIA_EXPORT VideoEncodeAccelerator {
// Video encoder functions.
// Returns a list of the supported codec profiles of the video encoder. This
// can be called before Initialize().
virtual std::vector<SupportedProfile> GetSupportedProfiles() = 0;
// Initializes the video encoder with specific configuration. Called once per
// encoder construction. This call is synchronous and returns true iff
// initialization is successful.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment